library(lubridate)
library(ggplot2)
library(dplyr)
library(plotly)
library(visdat)
library(tidyr)
library(data.table)
library(raster)
library(nnet)
library(purrr)
library(DataExplorer)
library(pscl)
library(tree)
library(rpart)
library(rpart.plot)
library(ISLR)
library(randomForest)
library(kableExtra)
library(broom)
library(rattle) 
# read our data
survey_data_test <- read.csv("cozie_responses_and_physiological_data_test_public.csv", 
                        sep = ",", header = TRUE)
# read our data
survey_data <- read.csv("cozie_responses_and_physiological_data_training.csv", 
                        sep = ",", header = TRUE)

weather_rainfall_data <- read.csv("weather_rainfall.csv", 
                        sep = ",", header = TRUE)

weather_wind_speed_data <- read.csv("weather_wind-speed.csv", 
                        sep = ",", header = TRUE)

weather_wind_direction_data <- read.csv("weather_wind-direction.csv", 
                        sep = ",", header = TRUE)

weather_stations_data <- read.csv("weather_stations.csv", 
                        sep = ",", header = TRUE)

weather_temperature_data <- read.csv("weather_air-temperature.csv", 
                        sep = ",", header = TRUE)

weather_humidity_data <- read.csv("weather_relative-humidity.csv", 
                        sep = ",", header = TRUE)
clean_weather_data <- function(weather_data){
  # remove rows that have only missing values and replace remaining NA values with average of row
  avg_temps <-
    rowMeans(subset(weather_data[rowSums(is.na(weather_data)) != ncol(weather_data),], select = c(-X)), na.rm = T)
  weather_data <-
    weather_data[rowSums(is.na(weather_data)) != ncol(weather_data),] %>%
    mutate(across(where(is.numeric),
                  ~ if_else(is.na(.), avg_temps, .)))
  
  weather_data <- weather_data %>% 
    mutate_at(vars(colnames(weather_data)[colnames(weather_data) != "X"]), as.numeric)

  return(weather_data)
}
weather_rainfall_data %>% plot_missing()

weather_rainfall_data_clean <-clean_weather_data(weather_rainfall_data)
weather_rainfall_data_clean %>% plot_missing()

weather_temperature_data %>% plot_missing()

weather_temperature_data_clean <-clean_weather_data(weather_temperature_data)
weather_temperature_data_clean %>% plot_missing()

weather_wind_direction_data %>% plot_missing()

weather_wind_direction_data_clean <-clean_weather_data(weather_wind_direction_data)
weather_wind_direction_data_clean %>% plot_missing()

weather_wind_speed_data %>% plot_missing()

weather_wind_speed_data_clean <-clean_weather_data(weather_wind_speed_data)
weather_wind_speed_data_clean %>% plot_missing()

weather_humidity_data %>% plot_missing()

weather_humidity_data_clean <-clean_weather_data(weather_humidity_data)
weather_humidity_data_clean %>% plot_missing()

weather_humidity_data_clean <- weather_humidity_data_clean %>% # convert time to time object
  #mutate(date_time = ymd_hms(weather_humidity_data_clean$X))
  mutate(date_time = ymd_hms(unlist(map(strsplit(weather_humidity_data_clean$X, split='+', fixed=TRUE), 1))))

weather_temperature_data_clean <- weather_temperature_data_clean %>% # convert time to time object
  #mutate(date_time = ymd_hms(weather_temperature_data_clean$X))
  mutate(date_time = ymd_hms(unlist(map(strsplit(weather_temperature_data_clean$X, split='+', fixed=TRUE), 1))))
  
weather_wind_direction_data_clean <- weather_wind_direction_data_clean %>% # convert time to time object
  #mutate(date_time = ymd_hms(weather_wind_direction_data_clean$X))
  mutate(date_time = ymd_hms(unlist(map(strsplit(weather_wind_direction_data_clean$X, split='+', fixed=TRUE), 1))))
  
weather_wind_speed_data_clean <- weather_wind_speed_data_clean %>% # convert time to time object
  #mutate(date_time = ymd_hms(weather_wind_speed_data_clean$X))
  mutate(date_time = ymd_hms(unlist(map(strsplit(weather_wind_speed_data_clean$X, split='+', fixed=TRUE), 1))))
  
weather_rainfall_data_clean <- weather_rainfall_data_clean %>% # convert time to time object
  #mutate(date_time = ymd_hms(weather_rainfall_data_clean$X))
  mutate(date_time = ymd_hms(unlist(map(strsplit(weather_rainfall_data_clean$X, split='+', fixed=TRUE), 1))))
  
inspect_weather_data <- function(weather_data, value_name){
  weather_start_date <- min(weather_data$date_time)
  weather_end_date <- max(weather_data$date_time)
  weather_duration <- max(weather_data$date_time) - min(weather_data$date_time)
  weather_frequency <- nrow(weather_data) / as.numeric(weather_duration)
  print(paste('First day of measurement', weather_start_date))
  print(paste('Last day of measurement', weather_end_date))
  print(paste('Duration of measurement in days', weather_duration))
  print(paste('Frequency of measurement per day', weather_frequency))
  
  weather_plot <-
    ggplot(data = as.data.frame(reshape2::melt(subset(weather_data, select = -c(X)), id="date_time")), aes(x = date_time, y = value, col = variable)) +
    geom_line() +
    theme_minimal() +
    theme() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
    xlab("Date") +
    ylab(value_name)
  
  return(weather_plot)
}
inspect_weather_data(weather_humidity_data_clean, "humidity %")
[1] "First day of measurement 2022-10-10 00:01:00"
[1] "Last day of measurement 2023-07-03 23:59:00"
[1] "Duration of measurement in days 266.998611111111"
[1] "Frequency of measurement per day 1384.83866437091"

inspect_weather_data(weather_rainfall_data_clean, "rainfall mm")
[1] "First day of measurement 2022-10-10 00:05:00"
[1] "Last day of measurement 2023-07-03 23:55:00"
[1] "Duration of measurement in days 266.993055555556"
[1] "Frequency of measurement per day 276.598954404765"

inspect_weather_data(weather_temperature_data_clean, "temperature °C")
[1] "First day of measurement 2022-10-10 00:01:00"
[1] "Last day of measurement 2023-07-03 23:59:00"
[1] "Duration of measurement in days 266.998611111111"
[1] "Frequency of measurement per day 1385.01094991131"

inspect_weather_data(weather_wind_direction_data_clean, "wind durection °")
[1] "First day of measurement 2022-10-10 00:01:00"
[1] "Last day of measurement 2023-07-03 23:59:00"
[1] "Duration of measurement in days 266.998611111111"
[1] "Frequency of measurement per day 1347.32910595665"

inspect_weather_data(weather_wind_speed_data_clean, "knots m/s")
[1] "First day of measurement 2022-10-10 00:01:00"
[1] "Last day of measurement 2023-07-03 23:59:00"
[1] "Duration of measurement in days 266.998611111111"
[1] "Frequency of measurement per day 1347.33285129448"

How many weather stations are there?

str(weather_stations_data)
'data.frame':   74 obs. of  5 variables:
 $ X        : int  0 1 2 3 4 5 6 7 8 9 ...
 $ id       : chr  "S07" "S08" "S100" "S102" ...
 $ name     : chr  "Lornie Road" "Upper Thomson Road" "Woodlands Road" "Semakau Landfill" ...
 $ latitude : num  1.34 1.37 1.42 1.19 1.44 ...
 $ longitude: num  104 104 104 104 104 ...

What are the variable names?

names(survey_data)
 [1] "time"                          "q_alone_group"                 "q_earphones"                  
 [4] "id_participant"                "ws_latitude"                   "q_location"                   
 [7] "q_location_office"             "q_location_transport"          "ws_longitude"                 
[10] "q_noise_kind"                  "q_noise_nearby"                "q_thermal_preference"         
[13] "ws_timestamp_location"         "ws_timestamp_start"            "ts_oxygen_saturation"         
[16] "ts_resting_heart_rate"         "ts_stand_time"                 "ts_step_count"                
[19] "ts_walking_distance"           "ws_survey_count"               "Footprint.Proportion"         
[22] "Footprint.Mean"                "Footprint.Stdev"               "Perimeter.Total"              
[25] "Perimeter.Mean"                "Perimeter.Stdev"               "Complexity.Mean"              
[28] "Complexity.Stdev"              "Building.Count"                "PopSum"                       
[31] "Men"                           "Women"                         "Elderly"                      
[34] "Youth"                         "Children"                      "Civic"                        
[37] "Commercial"                    "Entertainment"                 "Food"                         
[40] "Healthcare"                    "Institutional"                 "Recreational"                 
[43] "Social"                        "Green.View.Mean"               "Green.View.Stdev"             
[46] "Sky.View.Mean"                 "Sky.View.Stdev"                "Building.View.Mean"           
[49] "Building.View.Stdev"           "Road.View.Mean"                "Road.View.Stdev"              
[52] "Visual.Complexity.Mean"        "Visual.Complexity.Stdev"       "dT"                           
[55] "q_activity_category_alone"     "q_activity_category_group"     "ts_heart_rate"                
[58] "ts_audio_exposure_environment" "id_unique"                    
survey_data <- survey_data %>% 
  # convert time to time object
  #mutate(date_time = ymd_hms(survey_data$time)) %>% 
  mutate(date_time = ymd_hms(unlist(map(strsplit(survey_data$time, split='.', fixed=TRUE), 1)))) %>% 
  # replace empty strings with NA
  mutate(across(where(is.character), ~na_if(., ""))) 
head(survey_data)
str(survey_data)
'data.frame':   1149136 obs. of  60 variables:
 $ time                         : chr  "2022-10-10 09:32:04.588000+0800" "2022-10-10 09:32:04.588000+0800" "2022-10-10 09:33:08.713000+0800" "2022-10-10 09:35:11.600000+0800" ...
 $ q_alone_group                : chr  NA NA NA NA ...
 $ q_earphones                  : chr  NA NA NA NA ...
 $ id_participant               : chr  "xesh001" "xesh001" "xesh001" "xesh001" ...
 $ ws_latitude                  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ q_location                   : chr  NA NA NA NA ...
 $ q_location_office            : chr  NA NA NA NA ...
 $ q_location_transport         : chr  NA NA NA NA ...
 $ ws_longitude                 : num  NA NA NA NA NA NA NA NA NA NA ...
 $ q_noise_kind                 : chr  NA NA NA NA ...
 $ q_noise_nearby               : chr  NA NA NA NA ...
 $ q_thermal_preference         : chr  NA NA NA NA ...
 $ ws_timestamp_location        : chr  NA NA NA NA ...
 $ ws_timestamp_start           : chr  NA NA NA NA ...
 $ ts_oxygen_saturation         : num  NA NA NA NA NA NA NA NA NA NA ...
 $ ts_resting_heart_rate        : num  57 NA NA NA NA NA NA NA NA NA ...
 $ ts_stand_time                : num  NA NA NA NA NA NA NA NA NA NA ...
 $ ts_step_count                : num  NA NA NA NA NA NA NA NA NA NA ...
 $ ts_walking_distance          : num  NA NA NA NA NA NA NA NA NA NA ...
 $ ws_survey_count              : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Footprint.Proportion         : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Footprint.Mean               : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Footprint.Stdev              : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Perimeter.Total              : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Perimeter.Mean               : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Perimeter.Stdev              : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Complexity.Mean              : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Complexity.Stdev             : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Building.Count               : num  NA NA NA NA NA NA NA NA NA NA ...
 $ PopSum                       : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Men                          : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Women                        : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Elderly                      : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Youth                        : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Children                     : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Civic                        : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Commercial                   : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Entertainment                : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Food                         : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Healthcare                   : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Institutional                : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Recreational                 : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Social                       : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Green.View.Mean              : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Green.View.Stdev             : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Sky.View.Mean                : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Sky.View.Stdev               : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Building.View.Mean           : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Building.View.Stdev          : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Road.View.Mean               : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Road.View.Stdev              : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Visual.Complexity.Mean       : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Visual.Complexity.Stdev      : num  NA NA NA NA NA NA NA NA NA NA ...
 $ dT                           : num  NA NA NA NA NA NA NA NA NA NA ...
 $ q_activity_category_alone    : chr  NA NA NA NA ...
 $ q_activity_category_group    : chr  NA NA NA NA ...
 $ ts_heart_rate                : num  NA 83 59 61 63 67 74 76 78 90 ...
 $ ts_audio_exposure_environment: num  NA NA NA NA NA NA NA NA NA NA ...
 $ id_unique                    : int  1 2 3 4 5 6 7 8 9 10 ...
 $ date_time                    : POSIXct, format: "2022-10-10 09:32:04" "2022-10-10 09:32:04" "2022-10-10 09:33:08" "2022-10-10 09:35:11" ...

How many users are in the study? How many data points for each user?

nrow(table(survey_data$id_participant))
[1] 98
ggplot(survey_data[!is.na(survey_data$id_participant),],aes(id_participant)) +
  geom_bar(stat = "count", position = "dodge") +
  xlab("Participant Index") +
  ylab("Number of Responses") +
  scale_x_discrete(labels=seq(1,nrow(table(survey_data$id_participant)))) +
  theme_minimal()


# 
# ggsave("Number_of_Responses_per_partisipant.png",
#        width = 18, height = 6, dpi = 200, units = "in", device='png')
i1 <- which(!is.na(survey_data$q_thermal_preference))

logs <- data.frame(log_row_index = rownames(survey_data[i1,]), 
                   date_time = survey_data[i1,]$date_time)
grouped_logs <- merge(survey_data, logs, by = "date_time", all.x = TRUE) %>%
  arrange(date_time) %>% # sort by time
  fill(log_row_index, .direction = "down") %>% # fill group index
  fill(log_row_index, .direction = "up") %>% # fill group index
  group_by(id_participant) %>% # group by participant
  arrange(id_participant, date_time) %>% #sort by user id then by time
  group_by(log_row_index) # group by log group

nrow(grouped_logs)
[1] 1149184
avg_heart_rate_past_10 <- grouped_logs %>% # group by log interval
  summarize(average_heart_rate = mean(tail(na.omit(ts_heart_rate)), n = 10))

nrow(avg_heart_rate_past_10)
[1] 4900
total_dist_past_10 <- grouped_logs %>% # group by log interval
  summarize(dist_walked = sum(tail(na.omit(ts_walking_distance)), n = 10)) 
  # take mean of last 10 non NaN walked distance measures
nrow(total_dist_past_10)
[1] 4900
activity_data <- left_join(avg_heart_rate_past_10, 
                           total_dist_past_10, 
                           by = "log_row_index",
                           keep = FALSE)
head(activity_data)
activity_data_full <- survey_data[activity_data$log_row_index,]
activity_data_full$log_row_index <- activity_data$log_row_index
activity_data_full <- left_join(activity_data, activity_data_full, by = "log_row_index")
head(activity_data_full)
humidity_stations_ids <- colnames(weather_humidity_data_clean)[colnames(weather_humidity_data_clean) %in% weather_stations_data$id]
humidity_stations <- weather_stations_data[weather_stations_data$id %in% humidity_stations_ids, ]

d <- pointDistance(activity_data_full[,c("ws_longitude", "ws_latitude")], 
                   humidity_stations[,c("longitude", "latitude")], 
                   lonlat=TRUE, allpairs=T) 
i <- apply(d, 1, which.min)

activity_data_full$humidity_ID = humidity_stations$id[i]


rainfall_stations_ids <- colnames(weather_rainfall_data_clean)[colnames(weather_rainfall_data_clean) %in% weather_stations_data$id]
rainfall_stations <- weather_stations_data[weather_stations_data$id %in% rainfall_stations_ids, ]

d <- pointDistance(activity_data_full[,c("ws_longitude", "ws_latitude")], 
                   rainfall_stations[,c("longitude", "latitude")], 
                   lonlat=TRUE, allpairs=T) 
i <- apply(d, 1, which.min)

activity_data_full$rainfall_ID = rainfall_stations$id[i]


temperature_stations_ids <- colnames(weather_temperature_data_clean)[colnames(weather_temperature_data_clean) %in% weather_stations_data$id]
temperature_stations <- weather_stations_data[weather_stations_data$id %in% temperature_stations_ids, ]

d <- pointDistance(activity_data_full[,c("ws_longitude", "ws_latitude")], 
                   temperature_stations[,c("longitude", "latitude")], 
                   lonlat=TRUE, allpairs=T) 
i <- apply(d, 1, which.min)

activity_data_full$temperature_ID = temperature_stations$id[i]


wind_speed_stations_ids <- colnames(weather_wind_speed_data_clean)[colnames(weather_wind_speed_data_clean) %in% weather_stations_data$id]
wind_speed_stations <- weather_stations_data[weather_stations_data$id %in% wind_speed_stations_ids, ]

d <- pointDistance(activity_data_full[,c("ws_longitude", "ws_latitude")], 
                   wind_speed_stations[,c("longitude", "latitude")], 
                   lonlat=TRUE, allpairs=T) 
i <- apply(d, 1, which.min)

activity_data_full$wind_speed_ID = wind_speed_stations$id[i]


wind_direction_stations_ids <- colnames(weather_wind_direction_data_clean)[colnames(weather_wind_direction_data_clean) %in% weather_stations_data$id]
wind_direction_stations <- weather_stations_data[weather_stations_data$id %in% wind_direction_stations_ids, ]

d <- pointDistance(activity_data_full[,c("ws_longitude", "ws_latitude")], 
                   wind_direction_stations[,c("longitude", "latitude")], 
                   lonlat=TRUE, allpairs=T) 
i <- apply(d, 1, which.min)

activity_data_full$wind_direction_ID = wind_direction_stations$id[i]

head(activity_data_full)
str(weather_humidity_data_clean[,c(humidity_stations_ids,'date_time')])
'data.frame':   369750 obs. of  17 variables:
 $ S24      : num  78.2 77.7 76.6 76.3 75.9 75.4 75.6 75.1 75.7 77.1 ...
 $ S43      : num  82.2 81.2 82.5 82.6 82.8 ...
 $ S44      : num  81.4 81.4 81.6 82.1 82.6 82.5 82.5 82.4 82.4 82.3 ...
 $ S50      : num  83.3 83.4 83.4 83.3 83.2 83 83 83.2 83.3 83.7 ...
 $ S60      : num  67.1 67.3 67.8 68.4 67.9 67.7 67.6 67.3 66.7 66.5 ...
 $ S100     : num  84.8 84.9 85 85.1 85.1 85.2 85.4 85.4 85.4 85.5 ...
 $ S102     : num  81.3 81.2 81.3 81.2 81.2 ...
 $ S104     : num  84.5 84.5 84.5 84.4 84.3 84.6 84.6 84.5 84.4 84.2 ...
 $ S106     : num  89.5 89.4 89.3 89.3 89.2 89.1 89.2 89.3 89.2 89.2 ...
 $ S107     : num  75.1 75.8 76.4 75.8 76.3 75.5 75.6 76.4 76.6 78 ...
 $ S108     : num  87.2 87 87.2 87.6 87.9 88.2 88.5 88.7 88.9 89.1 ...
 $ S109     : num  82.5 82.6 82.5 82.3 82.1 81.9 81.6 81.5 81.5 81.5 ...
 $ S111     : num  78.9 78.8 78.3 78.3 78.5 78.3 78.3 77.8 77.9 77.9 ...
 $ S115     : num  71 70.9 71.5 72 71.6 71.7 72 72.1 72.4 72.5 ...
 $ S116     : num  87.1 87.2 86.5 85.5 85.2 85.4 85.8 87 87.8 88.4 ...
 $ S121     : num  86.2 86 86 85.7 85.8 85.6 85.4 85.6 86 86.1 ...
 $ date_time: POSIXct, format: "2022-10-10 00:01:00" "2022-10-10 00:02:00" "2022-10-10 00:03:00" "2022-10-10 00:04:00" ...
melted_humidity_data <- reshape2::melt(weather_humidity_data_clean[,c(humidity_stations_ids,'date_time')], id='date_time')
colnames(melted_humidity_data)[colnames(melted_humidity_data) == 'variable'] <- 'humidity_ID'
colnames(melted_humidity_data)[colnames(melted_humidity_data) == 'value'] <- 'humidity'
setDT(melted_humidity_data)

melted_rainfall_data <- reshape2::melt(weather_rainfall_data_clean[,c(rainfall_stations_ids,'date_time')], id='date_time')
colnames(melted_rainfall_data)[colnames(melted_rainfall_data) == 'variable'] <- 'rainfall_ID'
colnames(melted_rainfall_data)[colnames(melted_rainfall_data) == 'value'] <- 'rainfall'
setDT(melted_rainfall_data)

melted_temperature_data <- reshape2::melt(weather_temperature_data_clean[,c(temperature_stations_ids,'date_time')], id='date_time')
colnames(melted_temperature_data)[colnames(melted_temperature_data) == 'variable'] <- 'temperature_ID'
colnames(melted_temperature_data)[colnames(melted_temperature_data) == 'value'] <- 'temperature'
setDT(melted_temperature_data)

melted_wind_speed_data <- reshape2::melt(weather_wind_speed_data_clean[,c(wind_speed_stations_ids,'date_time')], id='date_time')
colnames(melted_wind_speed_data)[colnames(melted_wind_speed_data) == 'variable'] <- 'wind_speed_ID'
colnames(melted_wind_speed_data)[colnames(melted_wind_speed_data) == 'value'] <- 'wind_speed'
setDT(melted_wind_speed_data)

melted_wind_direction_data <- reshape2::melt(weather_wind_direction_data_clean[,c(wind_direction_stations_ids,'date_time')], id='date_time')
colnames(melted_wind_direction_data)[colnames(melted_wind_direction_data) == 'variable'] <- 'wind_direction_ID'
colnames(melted_wind_direction_data)[colnames(melted_wind_direction_data) == 'value'] <- 'wind_direction'
setDT(melted_wind_direction_data)
setDT(activity_data_full)

activity_data_full <- activity_data_full[, c("humidityTime", "humidity") := 
    melted_humidity_data[activity_data_full, on = c("humidity_ID", "date_time"), roll = Inf, .(x.date_time, x.humidity)]][]

activity_data_full <- activity_data_full[, c("rainfallTime", "rainfall") := 
    melted_rainfall_data[activity_data_full, on = c("rainfall_ID", "date_time"), roll = Inf, .(x.date_time, x.rainfall)]][]

activity_data_full <- activity_data_full[, c("temperatureTime", "temperature") := 
    melted_temperature_data[activity_data_full, on = c("temperature_ID", "date_time"), roll = Inf, .(x.date_time, x.temperature)]][]

activity_data_full <- activity_data_full[, c("wind_speedTime", "wind_speed") := 
    melted_wind_speed_data[activity_data_full, on = c("wind_speed_ID", "date_time"), roll = Inf, .(x.date_time, x.wind_speed)]][]

activity_data_full <- activity_data_full[, c("wind_directionTime", "wind_direction") := 
    melted_wind_direction_data[activity_data_full, on = c("wind_direction_ID", "date_time"), roll = Inf, .(x.date_time, x.wind_direction)]][]

head(activity_data_full, 100)
selected_data <- activity_data_full[,c(
  'id_participant',
  'ws_longitude',
  'ws_latitude',
  'dist_walked',
  'average_heart_rate',
  'q_location',
  'Green.View.Mean',
  'Footprint.Mean',
  'Perimeter.Mean',
  'Building.Count',
  'Sky.View.Mean',
  'Building.View.Mean',
  'Road.View.Mean',
  'humidity',
  'rainfall',
  'temperature',
  'wind_speed',
  'wind_direction',
  'q_thermal_preference',
  'date_time',
  'dT',
  'Visual.Complexity.Mean'
)]

selected_data <- drop_na(selected_data)


selected_data$q_location <- as.factor(selected_data$q_location)
selected_data$q_thermal_preference <-
  as.factor(selected_data$q_thermal_preference)
summary(selected_data)
 id_participant      ws_longitude    ws_latitude     dist_walked     average_heart_rate           q_location  
 Length:4379        Min.   :103.6   Min.   :1.246   Min.   :  10.0   Min.   : 43.50     Indoor - Class : 267  
 Class :character   1st Qu.:103.8   1st Qu.:1.297   1st Qu.: 107.7   1st Qu.: 72.17     Indoor - Home  :2039  
 Mode  :character   Median :103.8   Median :1.319   Median : 227.9   Median : 79.67     Indoor - Office: 704  
                    Mean   :103.8   Mean   :1.332   Mean   : 342.6   Mean   : 81.37     Indoor - Other : 581  
                    3rd Qu.:103.8   3rd Qu.:1.355   3rd Qu.: 472.1   3rd Qu.: 88.00     Outdoor        : 517  
                    Max.   :104.0   Max.   :1.454   Max.   :4173.2   Max.   :178.50     Transportation : 271  
 Green.View.Mean  Footprint.Mean  Perimeter.Mean   Building.Count   Sky.View.Mean    Building.View.Mean Road.View.Mean   
 Min.   :0.0000   Min.   :    0   Min.   :   0.0   Min.   :  0.00   Min.   :0.0000   Min.   :0.00000    Min.   :0.00000  
 1st Qu.:0.2670   1st Qu.: 1045   1st Qu.: 150.9   1st Qu.: 22.50   1st Qu.:0.1000   1st Qu.:0.07229    1st Qu.:0.09087  
 Median :0.3225   Median : 1357   Median : 191.7   Median : 32.00   Median :0.1453   Median :0.10687    Median :0.12242  
 Mean   :0.3204   Mean   : 2367   Mean   : 219.7   Mean   : 41.91   Mean   :0.1577   Mean   :0.12802    Mean   :0.12753  
 3rd Qu.:0.3836   3rd Qu.: 1834   3rd Qu.: 251.7   3rd Qu.: 44.00   3rd Qu.:0.2113   3rd Qu.:0.17388    3rd Qu.:0.15535  
 Max.   :0.6560   Max.   :64330   Max.   :1834.8   Max.   :395.00   Max.   :0.4772   Max.   :0.55050    Max.   :0.37500  
    humidity        rainfall         temperature     wind_speed     wind_direction q_thermal_preference
 Min.   :34.90   Min.   : 0.00000   Min.   :22.9   Min.   : 0.300   Min.   :  1    Cooler   :1736      
 1st Qu.:66.10   1st Qu.: 0.00000   1st Qu.:27.4   1st Qu.: 2.600   1st Qu.: 73    No change:2377      
 Median :74.40   Median : 0.00000   Median :29.2   Median : 4.200   Median :196    Warmer   : 266      
 Mean   :74.76   Mean   : 0.04111   Mean   :29.0   Mean   : 4.795   Mean   :185                        
 3rd Qu.:83.30   3rd Qu.: 0.00000   3rd Qu.:30.6   3rd Qu.: 6.200   3rd Qu.:286                        
 Max.   :99.50   Max.   :11.31200   Max.   :35.0   Max.   :17.400   Max.   :359                        
   date_time                            dT           Visual.Complexity.Mean
 Min.   :2022-10-10 13:13:19.00   Min.   :   55.00   Min.   :0.000         
 1st Qu.:2022-11-16 15:07:59.00   1st Qu.:   79.59   1st Qu.:1.601         
 Median :2023-03-29 17:45:24.00   Median :  121.10   Median :1.712         
 Mean   :2023-02-08 08:38:30.94   Mean   :  368.76   Mean   :1.694         
 3rd Qu.:2023-04-24 18:08:26.50   3rd Qu.:  275.25   3rd Qu.:1.793         
 Max.   :2023-05-30 17:00:31.00   Max.   :34368.42   Max.   :2.126         

library(DataExplorer)

selected_data %>% plot_histogram(ggtheme = theme_minimal()) 

selected_data_no_outliers <- selected_data[(
  selected_data$dT < 500 & # remove measurements that are infrequent
  selected_data$Footprint.Mean < 10000
   ),]


#selected_data_no_outliers <- selected_data_no_outliers[selected_data_no_outliers$q_location == "Outdoor", ]
selected_data_no_outliers %>% plot_histogram(ggtheme = theme_minimal()) 

ggplot(data=selected_data_no_outliers,aes(q_thermal_preference)) +
  geom_bar(aes(fill=as.factor(round(temperature)))) + theme_minimal() + 
  scale_color_gradient2(low = "blue", mid = "white", high = "red", space = "Lab" )

# creating a sample data.frame with your lat/lon points
lon <- selected_data_no_outliers$ws_longitude
lat <- selected_data_no_outliers$ws_latitude
thermal_preference <- selected_data_no_outliers$q_thermal_preference

df <- as.data.frame(cbind(lon,lat))

df <- df %>% 
  arrange(thermal_preference) %>% 
  mutate(thermal_preference = as.character(thermal_preference),
         color = recode(thermal_preference,'Cooler' = "red",
                        "No change" = "green", "Warmer" = "blue"))

fig <- df

fig <- fig %>%
  plot_ly(
    lat = ~lat,
    lon = ~lon,
    type = 'scattermapbox',
    mode = "markers",
    symbol  = ~thermal_preference,
    marker = list(color = ~color,size=5)) 


fig <- fig %>%
  layout(
    mapbox = list(
      style = 'open-street-map',
      zoom =9.5,
      center = list(lon = mean(df$lon), lat = mean(df$lat))))

pb <- plotly_build(fig)

pb
NA

ggplot(selected_data_no_outliers, aes(date_time, temperature)) + geom_point() + theme_minimal()

as.numeric(survey_data$date_time) %>%
  plot_histogram(ggtheme = theme_minimal())

# Temperature increases during the day
ggplot(selected_data[ selected_data$date_time > ymd("2022/10/18") &
                      selected_data$date_time < ymd("2022/10/19") #&
                     ,], aes(date_time, temperature)) + geom_point() + theme_minimal()

========================Model Testing Starts Here===============================

library(corrplot)
corrplot 0.92 loaded
selected_data_log <-
  subset(
    selected_data_no_outliers,
    select = -c(
      id_participant,
      ws_longitude,
      ws_latitude,
      Perimeter.Mean,
      humidity,
      wind_direction,
      Building.Count,
      dT
    )
  )


selected_data_log$q_thermal_preference <-
  as.factor(selected_data_log$q_thermal_preference == "Cooler")

selected_data_log$is_outdoor <-  
  as.factor(selected_data_log$q_location == "Outdoor")

# 
selected_data_log$is_winter <- as.factor(selected_data_log$date_time > ym("2023/04"))
selected_data_log$is_day <- as.factor((hour(selected_data_log$date_time) > 12 &
                                       hour(selected_data_log$date_time) < 18) == T)

selected_data_log <-
  subset(selected_data_log, select = -c(q_location, date_time))

set.seed(1)

# Divide the data into 80% training and 20% testing
train <-
  sample(1:nrow(selected_data_log),
         size = round(nrow(selected_data_log) * 0.8),
         replace = FALSE)

selected_data_log_train <- selected_data_log[train, ]
selected_data_log_test <- selected_data_log[-train, ]

selected_data_log %>%
  mutate(is_outdoor = as.numeric(is_outdoor)) %>%
  mutate(q_thermal_preference = as.numeric(q_thermal_preference)) %>%
  mutate(is_winter = as.numeric(is_winter)) %>%
  mutate(is_day = as.numeric(is_day)) %>%
  cor(use = "pairwise.complete.obs") %>%
  corrplot(order = 'alphabet', diag = F)

str(selected_data_log_train)
Classes ‘data.table’ and 'data.frame':  2678 obs. of  15 variables:
 $ dist_walked           : num  405.4 28.8 237 89.1 980.4 ...
 $ average_heart_rate    : num  75 75.3 69 79.7 90 ...
 $ Green.View.Mean       : num  0.265 0.405 0.367 0.259 0.276 ...
 $ Footprint.Mean        : num  1255 1435 1273 1802 1079 ...
 $ Sky.View.Mean         : num  0.106 0.132 0.152 0.191 0.288 ...
 $ Building.View.Mean    : num  0.2684 0.0782 0.032 0.1297 0.0589 ...
 $ Road.View.Mean        : num  0.1155 0.2062 0.1972 0.2013 0.0868 ...
 $ rainfall              : num  0 0 0 0 0 0 0 0 0 0 ...
 $ temperature           : num  25.5 29.7 26.4 30.7 29.3 27.9 32.5 30.9 26.4 26 ...
 $ wind_speed            : num  1.6 5.7 2.5 4.4 7.2 2 4.2 4.7 4.4 2.2 ...
 $ q_thermal_preference  : Factor w/ 2 levels "FALSE","TRUE": 1 2 2 1 1 2 1 1 2 1 ...
 $ Visual.Complexity.Mean: num  1.78 1.78 1.38 1.86 1.81 ...
 $ is_outdoor            : Factor w/ 2 levels "FALSE","TRUE": 1 1 1 1 1 1 2 2 1 1 ...
 $ is_winter             : Factor w/ 2 levels "FALSE","TRUE": 1 1 2 1 1 2 2 2 1 1 ...
 $ is_day                : Factor w/ 2 levels "FALSE","TRUE": 1 2 1 2 1 1 2 2 1 1 ...
 - attr(*, ".internal.selfref")=<externalptr> 
model <- glm(q_thermal_preference ~ . , family = binomial(link = "logit"), data = selected_data_log_train)
summary(model)

Call:
glm(formula = q_thermal_preference ~ ., family = binomial(link = "logit"), 
    data = selected_data_log_train)

Coefficients:
                         Estimate Std. Error z value Pr(>|z|)    
(Intercept)            -4.941e+00  8.657e-01  -5.708 1.15e-08 ***
dist_walked            -2.028e-04  1.211e-04  -1.674  0.09403 .  
average_heart_rate      2.274e-04  2.790e-03   0.081  0.93505    
Green.View.Mean         1.670e+00  5.934e-01   2.814  0.00489 ** 
Footprint.Mean         -4.790e-06  3.066e-05  -0.156  0.87585    
Sky.View.Mean           2.894e+00  6.642e-01   4.357 1.32e-05 ***
Building.View.Mean      2.149e+00  7.171e-01   2.997  0.00273 ** 
Road.View.Mean          1.055e+00  8.797e-01   1.199  0.23052    
rainfall               -2.109e-02  1.612e-01  -0.131  0.89593    
temperature             1.037e-01  2.086e-02   4.971 6.65e-07 ***
wind_speed              1.133e-02  1.454e-02   0.780  0.43557    
Visual.Complexity.Mean -6.921e-02  2.788e-01  -0.248  0.80392    
is_outdoorTRUE          7.059e-01  1.216e-01   5.807 6.37e-09 ***
is_winterTRUE           3.599e-01  8.951e-02   4.021 5.80e-05 ***
is_dayTRUE             -1.947e-01  8.453e-02  -2.303  0.02125 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 3578.7  on 2677  degrees of freedom
Residual deviance: 3452.2  on 2663  degrees of freedom
AIC: 3482.2

Number of Fisher Scoring iterations: 4
model2 <-
  glm(
    q_thermal_preference ~ is_winter + is_outdoor + is_day + temperature + Sky.View.Mean,
    family = binomial(link = "logit"),
    data = selected_data_log_train
  )
summary(model2)

Call:
glm(formula = q_thermal_preference ~ is_winter + is_outdoor + 
    is_day + temperature + Sky.View.Mean, family = binomial(link = "logit"), 
    data = selected_data_log_train)

Coefficients:
               Estimate Std. Error z value Pr(>|z|)    
(Intercept)    -3.88353    0.57885  -6.709 1.96e-11 ***
is_winterTRUE   0.35545    0.08829   4.026 5.67e-05 ***
is_outdoorTRUE  0.68305    0.12022   5.682 1.33e-08 ***
is_dayTRUE     -0.19882    0.08324  -2.389  0.01691 *  
temperature     0.10306    0.02025   5.090 3.58e-07 ***
Sky.View.Mean   1.59007    0.51023   3.116  0.00183 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 3578.7  on 2677  degrees of freedom
Residual deviance: 3466.4  on 2672  degrees of freedom
AIC: 3478.4

Number of Fisher Scoring iterations: 4
# Step 3: Predict probabilities
probabilities <- predict(model2, selected_data_log_test, type = "response")

# Step 4 and 5: Use different cutoffs and calculate accuracy
cutoffs <- c(0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9)

accuracies <- sapply(cutoffs, function(cutoff) {
  # Convert probabilities to binary predictions
  predictions <- ifelse(probabilities > cutoff, 2, 1)
  
  # Calculate accuracy
  accuracy(as.numeric(selected_data_log_test$q_thermal_preference), predictions)
  #mean(predictions == as.numeric(selected_data_log_test$q_thermal_preference))
})

# Print the accuracies for each cutoff
names(accuracies) <- cutoffs
accuracies
      0.2       0.3       0.4       0.5       0.6       0.7       0.8       0.9 
0.4059701 0.4701493 0.6029851 0.6238806 0.6208955 0.6014925 0.6000000 0.6000000 
pR2(model2)['McFadden']
fitting null model for pseudo-r2
  McFadden 
0.03139527 

fit.tree = rpart(q_thermal_preference ~ ., data=selected_data_log_train, method="class", cp=0.008)
prp(fit.tree,
    main = "Tree model for predicting if thermal preference is \"Cooler\"",
    box.palette = "auto",
    fallen.leaves = F,  
    shadow.col = "gray",   
    branch.lty = 3,        
    branch = .5,           
    faclen = 0,            
    round = 0)

printcp(fit.tree)

Classification tree:
rpart(formula = q_thermal_preference ~ ., data = selected_data_log_train, 
    method = "class", cp = 0.008)

Variables actually used in tree construction:
[1] Building.View.Mean is_outdoor         is_winter          Sky.View.Mean      temperature       

Root node error: 1041/2678 = 0.38872

n= 2678 

        CP nsplit rel error  xerror     xstd
1 0.023055      0   1.00000 1.00000 0.024232
2 0.010567      5   0.86647 0.94524 0.023966
3 0.008000      6   0.85591 0.93084 0.023888

bestcp <- fit.tree$cptable[which.min(fit.tree$cptable[,"xerror"]),"CP"]
pruned.tree <- prune(fit.tree, cp = bestcp)

prp(pruned.tree,
    main = "Tree model for predicting if thermal preference is \"Cooler\"",
    box.palette = "auto",
    fallen.leaves = F,  
    shadow.col = "gray",   
    branch.lty = 3,        
    branch = .5,           
    faclen = 0,            
    round = 0)

predicted <- as.numeric(predict(pruned.tree, selected_data_log_test, type = "class"))
sum(predicted == as.numeric(selected_data_log_test$q_thermal_preference)) / nrow(selected_data_log_test)
[1] 0.6641791
Metrics::accuracy(as.numeric(selected_data_log_test$q_thermal_preference), predicted)
[1] 0.6641791
SS_tot <- sum((as.numeric(selected_data_log_train$q_thermal_preference) - mean(as.numeric(selected_data_log_train$q_thermal_preference))) ^ 2)
SS_res_tree <- sum((as.numeric(selected_data_log_train$q_thermal_preference) - as.numeric(predict(pruned.tree, selected_data_log_train, type = "class"))) ^ 2)

R_sq_lm <- 1 - SS_res_tree / SS_tot
R_sq_lm
[1] -0.4001961
set.seed(50)

model_forest <-
  randomForest(
    q_thermal_preference ~ . ,
    data = selected_data_log_train,
    importance = TRUE,
    ntree = 150
  )

predicted <- as.numeric(predict(model_forest, selected_data_log_test))
sum(predicted == as.numeric(selected_data_log_test$q_thermal_preference)) / nrow(selected_data_log_test)
[1] 0.7059701
Metrics::accuracy(as.numeric(selected_data_log_test$q_thermal_preference), predicted)
[1] 0.7059701
randomForest::importance(model_forest)
                            FALSE        TRUE MeanDecreaseAccuracy MeanDecreaseGini
dist_walked             0.8563182  1.37172663            1.4746404        124.84328
average_heart_rate     -0.8833980  0.05711924           -0.6839402        116.39439
Green.View.Mean        12.0010150  9.75821062           16.8150583        103.87575
Footprint.Mean         13.6651554  9.88235906           17.5411607        129.76265
Sky.View.Mean          13.7253629 12.71720495           19.0889554        120.27174
Building.View.Mean     12.2702192 10.35629188           18.4378167        108.88783
Road.View.Mean         13.4612004 13.76639433           18.6040803        110.00430
rainfall                3.7616951  0.46017916            3.8177337         11.21246
temperature             9.3007950  5.27828073           10.9330602        127.13448
wind_speed              5.9826921  0.98871531            5.3589595        113.41158
Visual.Complexity.Mean 13.9860610 12.11331136           16.6666900        120.65905
is_outdoor              9.1658534 13.02425758           14.0035736         25.65583
is_winter              11.5291223 10.40958961           13.1356087         21.58707
is_day                  0.1084446  1.07094103            0.7524310         20.35907
SS_tot <- sum((as.numeric(selected_data_log_train$q_thermal_preference) - mean(as.numeric(selected_data_log_train$q_thermal_preference))) ^ 2)
SS_res_tree <- sum((as.numeric(selected_data_log_train$q_thermal_preference) - as.numeric(predict(model_forest, selected_data_log_train))) ^ 2)

R_sq_lm <- 1 - SS_res_tree / SS_tot
R_sq_lm
[1] 0.9984285

selected_data_multinom <-
  subset(
    selected_data_no_outliers,
    select = -c(
      id_participant,
      ws_longitude,
      ws_latitude,
      Perimeter.Mean,
      wind_direction,
      Building.Count,
      dT
    )
  )


selected_data_multinom$q_thermal_preference <-
  as.factor(selected_data_multinom$q_thermal_preference)

selected_data_multinom$is_outdoor <-  
  as.factor(selected_data_multinom$q_location == "Outdoor")

selected_data_multinom$is_winter <- as.factor(selected_data_multinom$date_time > ym("2023/04"))
selected_data_multinom$is_day <-  as.factor((hour(selected_data_multinom$date_time) > 12 &
                                             hour(selected_data_multinom$date_time) < 18) == T)

selected_data_multinom <-
  subset(selected_data_multinom, select = -c(q_location, date_time))

set.seed(2)

# Divide the data into 80% training and 20% testing
train <-
  sample(1:nrow(selected_data_multinom),
         size = round(nrow(selected_data_multinom) * 0.8),
         replace = FALSE)

selected_data_multinom_train <- selected_data_multinom[train, ]
selected_data_multinom_test <- selected_data_multinom[-train, ]

selected_data_multinom %>%
  mutate(is_outdoor = as.numeric(is_outdoor)) %>%
  mutate(q_thermal_preference = as.numeric(q_thermal_preference)) %>%
  mutate(is_winter = as.numeric(is_winter)) %>%
  mutate(is_day = as.numeric(is_day)) %>%
  cor(use = "pairwise.complete.obs") %>%
  corrplot(order = 'alphabet', diag = F)

model_multinom <- multinom(q_thermal_preference ~ ., data = selected_data_multinom_train)
# weights:  51 (32 variable)
initial  value 2942.083709 
iter  10 value 2621.325508
iter  20 value 2333.878059
iter  30 value 2262.114329
iter  40 value 2261.403953
final  value 2261.403665 
converged
summary(model_multinom)
Call:
multinom(formula = q_thermal_preference ~ ., data = selected_data_multinom_train)

Coefficients:
          (Intercept)   dist_walked average_heart_rate Green.View.Mean Footprint.Mean Sky.View.Mean Building.View.Mean
No change   3.7752521  0.0002199776       -0.001969002       -1.199221   2.388761e-06     -2.831785         -2.2248894
Warmer      0.4628009 -0.0006249347        0.002440529       -1.283316   1.008029e-04     -1.112459          0.8609189
          Road.View.Mean     humidity    rainfall temperature   wind_speed Visual.Complexity.Mean is_outdoorTRUE
No change      -1.000906  0.004302523 0.002536116 -0.09158742 -0.003618452              0.2782135     -0.6701643
Warmer          2.105141 -0.001101559 0.122157617 -0.02482563  0.048528087             -0.9386555     -0.4311649
          is_winterTRUE is_dayTRUE
No change    -0.3502048  0.1534436
Warmer       -0.4710044  0.3879659

Std. Errors:
           (Intercept)  dist_walked average_heart_rate Green.View.Mean Footprint.Mean Sky.View.Mean Building.View.Mean
No change 0.0014990947 0.0001231608        0.002853019    0.0012844538   3.176422e-05  0.0023649486       0.0008636186
Warmer    0.0005027521 0.0002912159        0.005606995    0.0003877144   5.492044e-05  0.0004844795       0.0003588285
          Road.View.Mean    humidity   rainfall temperature wind_speed Visual.Complexity.Mean is_outdoorTRUE is_winterTRUE
No change   0.0006949420 0.002684677 0.10679755 0.009805298 0.01436564            0.004708234     0.11756837    0.08220748
Warmer      0.0001798301 0.005160460 0.04063834 0.017780836 0.02636042            0.001055362     0.01780133    0.01578459
          is_dayTRUE
No change 0.07980995
Warmer    0.01499350

Residual Deviance: 4522.807 
AIC: 4586.807 
tidy(model_multinom, conf.int = TRUE) %>% 
  kable() %>% 
  kable_styling("basic", full_width = FALSE)
y.level term estimate std.error statistic p.value conf.low conf.high
No change (Intercept) 3.7752521 0.0014991 2518.3546648 0.0000000 3.7723139 3.7781903
No change dist_walked 0.0002200 0.0001232 1.7861005 0.0740830 -0.0000214 0.0004614
No change average_heart_rate -0.0019690 0.0028530 -0.6901468 0.4901019 -0.0075608 0.0036228
No change Green.View.Mean -1.1992206 0.0012845 -933.6424328 0.0000000 -1.2017381 -1.1967031
No change Footprint.Mean 0.0000024 0.0000318 0.0752029 0.9400533 -0.0000599 0.0000646
No change Sky.View.Mean -2.8317855 0.0023649 -1197.3983070 0.0000000 -2.8364207 -2.8271503
No change Building.View.Mean -2.2248894 0.0008636 -2576.2409104 0.0000000 -2.2265821 -2.2231968
No change Road.View.Mean -1.0009062 0.0006949 -1440.2730335 0.0000000 -1.0022682 -0.9995441
No change humidity 0.0043025 0.0026847 1.6026222 0.1090181 -0.0009593 0.0095644
No change rainfall 0.0025361 0.1067976 0.0237469 0.9810545 -0.2067832 0.2118555
No change temperature -0.0915874 0.0098053 -9.3406054 0.0000000 -0.1108055 -0.0723694
No change wind_speed -0.0036185 0.0143656 -0.2518825 0.8011319 -0.0317746 0.0245377
No change Visual.Complexity.Mean 0.2782135 0.0047082 59.0908400 0.0000000 0.2689856 0.2874415
No change is_outdoorTRUE -0.6701643 0.1175684 -5.7002093 0.0000000 -0.9005941 -0.4397345
No change is_winterTRUE -0.3502048 0.0822075 -4.2600111 0.0000204 -0.5113285 -0.1890811
No change is_dayTRUE 0.1534436 0.0798100 1.9226122 0.0545288 -0.0029810 0.3098682
Warmer (Intercept) 0.4628009 0.0005028 920.5351224 0.0000000 0.4618156 0.4637863
Warmer dist_walked -0.0006249 0.0002912 -2.1459497 0.0318770 -0.0011957 -0.0000542
Warmer average_heart_rate 0.0024405 0.0056070 0.4352651 0.6633700 -0.0085490 0.0134300
Warmer Green.View.Mean -1.2833160 0.0003877 -3309.9517812 0.0000000 -1.2840759 -1.2825561
Warmer Footprint.Mean 0.0001008 0.0000549 1.8354347 0.0664413 -0.0000068 0.0002084
Warmer Sky.View.Mean -1.1124589 0.0004845 -2296.1938892 0.0000000 -1.1134085 -1.1115094
Warmer Building.View.Mean 0.8609189 0.0003588 2399.2487940 0.0000000 0.8602156 0.8616222
Warmer Road.View.Mean 2.1051415 0.0001798 11706.2797585 0.0000000 2.1047890 2.1054939
Warmer humidity -0.0011016 0.0051605 -0.2134615 0.8309670 -0.0112159 0.0090128
Warmer rainfall 0.1221576 0.0406383 3.0059696 0.0026474 0.0425079 0.2018073
Warmer temperature -0.0248256 0.0177808 -1.3962017 0.1626538 -0.0596754 0.0100242
Warmer wind_speed 0.0485281 0.0263604 1.8409453 0.0656296 -0.0031374 0.1001936
Warmer Visual.Complexity.Mean -0.9386555 0.0010554 -889.4156712 0.0000000 -0.9407240 -0.9365870
Warmer is_outdoorTRUE -0.4311649 0.0178013 -24.2209411 0.0000000 -0.4660549 -0.3962749
Warmer is_winterTRUE -0.4710044 0.0157846 -29.8395155 0.0000000 -0.5019416 -0.4400672
Warmer is_dayTRUE 0.3879659 0.0149935 25.8756094 0.0000000 0.3585792 0.4173527
model_multinom2 <- multinom(q_thermal_preference ~ is_outdoor + is_winter + temperature + humidity + Green.View.Mean + Sky.View.Mean + Building.View.Mean + Road.View.Mean, data = selected_data_multinom_train)
# weights:  30 (18 variable)
initial  value 2942.083709 
iter  10 value 2339.496857
iter  20 value 2276.639460
final  value 2276.637693 
converged
summary(model_multinom)
Call:
multinom(formula = q_thermal_preference ~ ., data = selected_data_multinom_train)

Coefficients:
          (Intercept)   dist_walked average_heart_rate Green.View.Mean Footprint.Mean Sky.View.Mean Building.View.Mean
No change   3.7752521  0.0002199776       -0.001969002       -1.199221   2.388761e-06     -2.831785         -2.2248894
Warmer      0.4628009 -0.0006249347        0.002440529       -1.283316   1.008029e-04     -1.112459          0.8609189
          Road.View.Mean     humidity    rainfall temperature   wind_speed Visual.Complexity.Mean is_outdoorTRUE
No change      -1.000906  0.004302523 0.002536116 -0.09158742 -0.003618452              0.2782135     -0.6701643
Warmer          2.105141 -0.001101559 0.122157617 -0.02482563  0.048528087             -0.9386555     -0.4311649
          is_winterTRUE is_dayTRUE
No change    -0.3502048  0.1534436
Warmer       -0.4710044  0.3879659

Std. Errors:
           (Intercept)  dist_walked average_heart_rate Green.View.Mean Footprint.Mean Sky.View.Mean Building.View.Mean
No change 0.0014990947 0.0001231608        0.002853019    0.0012844538   3.176422e-05  0.0023649486       0.0008636186
Warmer    0.0005027521 0.0002912159        0.005606995    0.0003877144   5.492044e-05  0.0004844795       0.0003588285
          Road.View.Mean    humidity   rainfall temperature wind_speed Visual.Complexity.Mean is_outdoorTRUE is_winterTRUE
No change   0.0006949420 0.002684677 0.10679755 0.009805298 0.01436564            0.004708234     0.11756837    0.08220748
Warmer      0.0001798301 0.005160460 0.04063834 0.017780836 0.02636042            0.001055362     0.01780133    0.01578459
          is_dayTRUE
No change 0.07980995
Warmer    0.01499350

Residual Deviance: 4522.807 
AIC: 4586.807 
tidy(model_multinom2, conf.int = TRUE) %>% 
  kable() %>% 
  kable_styling("basic", full_width = FALSE)
y.level term estimate std.error statistic p.value conf.low conf.high
No change (Intercept) 4.1357486 1.7413921 2.3749669 0.0175505 0.7226828 7.5488145
No change is_outdoorTRUE -0.6759504 0.1230572 -5.4929750 0.0000000 -0.9171381 -0.4347626
No change is_winterTRUE -0.3716983 0.0916106 -4.0573721 0.0000496 -0.5512517 -0.1921448
No change temperature -0.0870918 0.0406158 -2.1442827 0.0320102 -0.1666974 -0.0074863
No change humidity 0.0038375 0.0072002 0.5329792 0.5940480 -0.0102745 0.0179496
No change Green.View.Mean -1.3402841 0.5842772 -2.2939181 0.0217952 -2.4854464 -0.1951218
No change Sky.View.Mean -2.7604770 0.6583995 -4.1927079 0.0000276 -4.0509164 -1.4700376
No change Building.View.Mean -2.1534922 0.7238279 -2.9751438 0.0029285 -3.5721689 -0.7348155
No change Road.View.Mean -0.7749980 0.9030069 -0.8582415 0.3907591 -2.5448589 0.9948630
Warmer (Intercept) -2.1556697 3.3693787 -0.6397825 0.5223140 -8.7595307 4.4481913
Warmer is_outdoorTRUE -0.4533492 0.2486565 -1.8231946 0.0682739 -0.9407070 0.0340086
Warmer is_winterTRUE -0.4751693 0.1821329 -2.6089147 0.0090830 -0.8321433 -0.1181953
Warmer temperature 0.0289362 0.0789534 0.3664977 0.7139937 -0.1258096 0.1836820
Warmer humidity 0.0057392 0.0138387 0.4147213 0.6783459 -0.0213841 0.0328626
Warmer Green.View.Mean -1.3286374 1.1668583 -1.1386451 0.2548512 -3.6156377 0.9583628
Warmer Sky.View.Mean -2.3908784 1.3161961 -1.8165062 0.0692928 -4.9705754 0.1888186
Warmer Building.View.Mean -0.1337018 1.3852025 -0.0965215 0.9231064 -2.8486488 2.5812451
Warmer Road.View.Mean 1.7403543 1.7774074 0.9791533 0.3275042 -1.7433001 5.2240087
predicted <-
  predict(model_multinom2, selected_data_multinom_test, type="class")
sum(predicted == selected_data_multinom_test$q_thermal_preference) / nrow(selected_data_multinom_test)
[1] 0.561194
pR2(model_multinom2)['McFadden']
fitting null model for pseudo-r2
# weights:  6 (2 variable)
initial  value 2942.083709 
final  value 2338.881449 
converged
  McFadden 
0.02661262 
Metrics::accuracy(selected_data_multinom_test$q_thermal_preference, predict(model_multinom2, selected_data_multinom_test, type="class"))
[1] 0.561194
table(predict(model_multinom2, selected_data_multinom_test, type = "class"))

   Cooler No change    Warmer 
      157       513         0 

fit.tree_multinom = rpart(q_thermal_preference ~ ., data=selected_data_multinom_train, method="class", cp=0.008)
prp(fit.tree_multinom,
    main = "Tree model for predicting actual thermal preference",
    box.palette = "auto",
    fallen.leaves = F,  
    shadow.col = "gray",   
    branch.lty = 3,        
    branch = .5,           
    faclen = 0,            
    round = 0)

fit.tree_multinom$variable.importance
           temperature               humidity          Sky.View.Mean             is_outdoor         Footprint.Mean 
           26.08085381            24.23874296            14.79659833            13.51492219            11.84085355 
Visual.Complexity.Mean     Building.View.Mean        Green.View.Mean              is_winter         Road.View.Mean 
            4.19756349             3.52949344             3.24815609             3.11076425             0.64676087 
            wind_speed     average_heart_rate 
            0.20953368             0.09875442 

bestcp_multinom <- fit.tree_multinom$cptable[which.min(fit.tree_multinom$cptable[,"xerror"]),"CP"]
bestcp_multinom
[1] 0.008
final_tree_model <- prune(fit.tree_multinom, cp = bestcp_multinom)
final_tree_model$variable.importance
           temperature               humidity          Sky.View.Mean             is_outdoor         Footprint.Mean 
           26.08085381            24.23874296            14.79659833            13.51492219            11.84085355 
Visual.Complexity.Mean     Building.View.Mean        Green.View.Mean              is_winter         Road.View.Mean 
            4.19756349             3.52949344             3.24815609             3.11076425             0.64676087 
            wind_speed     average_heart_rate 
            0.20953368             0.09875442 
prp(final_tree_model,
    main = "Tree model for predicting actual thermal preference",
    box.palette = "auto",
    fallen.leaves = F,  
    shadow.col = "gray",   
    branch.lty = 3,        
    branch = .5,           
    faclen = 0,            
    round = 0)

final_tree_model <-
  rpart(
    q_thermal_preference ~ 
      Visual.Complexity.Mean + 
      Footprint.Mean + 
      Sky.View.Mean + 
      Green.View.Mean + 
      Road.View.Mean + 
      Sky.View.Mean + 
      temperature +
      humidity + 
      is_outdoor,
    data = selected_data_multinom_train,
    method = "class",
    cp = 0.008
  )
prp(final_tree_model,
    main = "Tree model for predicting actual thermal preference",
    box.palette = "auto",
    fallen.leaves = F,  
    shadow.col = "gray",   
    branch.lty = 3,        
    branch = .5,           
    faclen = 0,            
    round = 0)

predicted <- as.numeric(predict(final_tree_model, selected_data_multinom_test, type = "class"))
sum(predicted == as.numeric(selected_data_multinom_test$q_thermal_preference)) / nrow(selected_data_multinom_test)
[1] 0.5641791
table(predict(final_tree_model, selected_data_multinom_test, type = "class"))

   Cooler No change    Warmer 
      221       449         0 
set.seed(50)

model_forest_multinom <-
  randomForest(
    q_thermal_preference ~ . -average_heart_rate -dist_walked -rainfall -wind_speed,
    data = selected_data_multinom_train,
    importance = TRUE,
    ntree = 200
  )

predicted <- as.numeric(predict(model_forest_multinom, selected_data_multinom_test))
sum(predicted == as.numeric(selected_data_multinom_test$q_thermal_preference)) / nrow(selected_data_multinom_test)
[1] 0.6537313
randomForest::importance(model_forest_multinom)
                           Cooler No change    Warmer MeanDecreaseAccuracy MeanDecreaseGini
Green.View.Mean        14.4061414 17.415267 11.719994            23.938412        140.96915
Footprint.Mean         17.6342879 15.588526 12.198786            24.695391        181.56286
Sky.View.Mean          20.8624277 19.429347 12.203289            31.269214        153.64321
Building.View.Mean     18.0361932 18.736024  9.164805            27.435648        149.70710
Road.View.Mean         22.3109888 21.242090 12.353233            31.763268        149.59443
humidity                8.5017913 13.330698  3.244791            17.787586        203.45012
temperature            11.1749083 12.814978  1.724296            17.150709        186.22944
Visual.Complexity.Mean 19.8946811 19.867053 13.797653            27.315548        153.70236
is_outdoor             12.2169349  8.714548  2.318363            14.959958         28.69651
is_winter              13.4873383 14.814294  6.457539            16.964393         26.44489
is_day                 -0.1618654  1.811180  1.614403             1.500995         32.63674
table(predict(model_forest_multinom, selected_data_multinom_test, type = "class"))

   Cooler No change    Warmer 
      233       426        11 
table(selected_data_multinom_test$q_thermal_preference)

   Cooler No change    Warmer 
      282       346        42 
# if we predict "No Change" we will be correct 51% of the time
sum(selected_data_multinom_test$q_thermal_preference == "No change") / nrow(selected_data_multinom_test)
[1] 0.5164179

========================Model Testing Ends Here=================================

LS0tDQp0aXRsZTogIk1HVDYyMDMgR3JvdXAgUHJvamVjdCINCnN1YnRpdGxlOiBGaW5hbCBSZXBvcnQNCmRhdGU6ICIwNC8yMS8yMDI0Ig0Kb3V0cHV0OiANCiAgZ2l0aHViX2RvY3VtZW50Og0KICAgIHRoZW1lOiBzYW5kc3RvbmUNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdGhlbWU6IHNhbmRzdG9uZQ0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgcGRmX2RvY3VtZW50Og0KICAgIGtlZXBfdGV4OiB0cnVlDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KLS0tDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeSh2aXNkYXQpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShyYXN0ZXIpDQpsaWJyYXJ5KG5uZXQpDQpsaWJyYXJ5KHB1cnJyKQ0KbGlicmFyeShEYXRhRXhwbG9yZXIpDQpsaWJyYXJ5KHBzY2wpDQpsaWJyYXJ5KHRyZWUpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShJU0xSKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShyYXR0bGUpCQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoTWV0cmljcykNCmBgYA0KDQpgYGB7cn0NCiMgcmVhZCBvdXIgZGF0YQ0Kc3VydmV5X2RhdGFfdGVzdCA8LSByZWFkLmNzdigiY296aWVfcmVzcG9uc2VzX2FuZF9waHlzaW9sb2dpY2FsX2RhdGFfdGVzdF9wdWJsaWMuY3N2IiwgDQogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiLCIsIGhlYWRlciA9IFRSVUUpDQpgYGANCg0KDQpgYGB7cn0NCiMgcmVhZCBvdXIgZGF0YQ0Kc3VydmV5X2RhdGEgPC0gcmVhZC5jc3YoImNvemllX3Jlc3BvbnNlc19hbmRfcGh5c2lvbG9naWNhbF9kYXRhX3RyYWluaW5nLmNzdiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiwiLCBoZWFkZXIgPSBUUlVFKQ0KYGBgDQoNCg0KYGBge3J9DQoNCndlYXRoZXJfcmFpbmZhbGxfZGF0YSA8LSByZWFkLmNzdigid2VhdGhlcl9yYWluZmFsbC5jc3YiLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIsIiwgaGVhZGVyID0gVFJVRSkNCg0Kd2VhdGhlcl93aW5kX3NwZWVkX2RhdGEgPC0gcmVhZC5jc3YoIndlYXRoZXJfd2luZC1zcGVlZC5jc3YiLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIsIiwgaGVhZGVyID0gVFJVRSkNCg0Kd2VhdGhlcl93aW5kX2RpcmVjdGlvbl9kYXRhIDwtIHJlYWQuY3N2KCJ3ZWF0aGVyX3dpbmQtZGlyZWN0aW9uLmNzdiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiwiLCBoZWFkZXIgPSBUUlVFKQ0KDQp3ZWF0aGVyX3N0YXRpb25zX2RhdGEgPC0gcmVhZC5jc3YoIndlYXRoZXJfc3RhdGlvbnMuY3N2IiwgDQogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiLCIsIGhlYWRlciA9IFRSVUUpDQoNCndlYXRoZXJfdGVtcGVyYXR1cmVfZGF0YSA8LSByZWFkLmNzdigid2VhdGhlcl9haXItdGVtcGVyYXR1cmUuY3N2IiwgDQogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiLCIsIGhlYWRlciA9IFRSVUUpDQoNCndlYXRoZXJfaHVtaWRpdHlfZGF0YSA8LSByZWFkLmNzdigid2VhdGhlcl9yZWxhdGl2ZS1odW1pZGl0eS5jc3YiLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIsIiwgaGVhZGVyID0gVFJVRSkNCg0KYGBgDQoNCg0KYGBge3J9DQpjbGVhbl93ZWF0aGVyX2RhdGEgPC0gZnVuY3Rpb24od2VhdGhlcl9kYXRhKXsNCiAgIyByZW1vdmUgcm93cyB0aGF0IGhhdmUgb25seSBtaXNzaW5nIHZhbHVlcyBhbmQgcmVwbGFjZSByZW1haW5pbmcgTkEgdmFsdWVzIHdpdGggYXZlcmFnZSBvZiByb3cNCiAgYXZnX3RlbXBzIDwtDQogICAgcm93TWVhbnMoc3Vic2V0KHdlYXRoZXJfZGF0YVtyb3dTdW1zKGlzLm5hKHdlYXRoZXJfZGF0YSkpICE9IG5jb2wod2VhdGhlcl9kYXRhKSxdLCBzZWxlY3QgPSBjKC1YKSksIG5hLnJtID0gVCkNCiAgd2VhdGhlcl9kYXRhIDwtDQogICAgd2VhdGhlcl9kYXRhW3Jvd1N1bXMoaXMubmEod2VhdGhlcl9kYXRhKSkgIT0gbmNvbCh3ZWF0aGVyX2RhdGEpLF0gJT4lDQogICAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwNCiAgICAgICAgICAgICAgICAgIH4gaWZfZWxzZShpcy5uYSguKSwgYXZnX3RlbXBzLCAuKSkpDQogIA0KICB3ZWF0aGVyX2RhdGEgPC0gd2VhdGhlcl9kYXRhICU+JSANCiAgICBtdXRhdGVfYXQodmFycyhjb2xuYW1lcyh3ZWF0aGVyX2RhdGEpW2NvbG5hbWVzKHdlYXRoZXJfZGF0YSkgIT0gIlgiXSksIGFzLm51bWVyaWMpDQoNCiAgcmV0dXJuKHdlYXRoZXJfZGF0YSkNCn0NCmBgYA0KDQpgYGB7cn0NCndlYXRoZXJfcmFpbmZhbGxfZGF0YSAlPiUgcGxvdF9taXNzaW5nKCkNCmBgYA0KYGBge3J9DQp3ZWF0aGVyX3JhaW5mYWxsX2RhdGFfY2xlYW4gPC1jbGVhbl93ZWF0aGVyX2RhdGEod2VhdGhlcl9yYWluZmFsbF9kYXRhKQ0Kd2VhdGhlcl9yYWluZmFsbF9kYXRhX2NsZWFuICU+JSBwbG90X21pc3NpbmcoKQ0KYGBgDQpgYGB7cn0NCndlYXRoZXJfdGVtcGVyYXR1cmVfZGF0YSAlPiUgcGxvdF9taXNzaW5nKCkNCmBgYA0KYGBge3J9DQp3ZWF0aGVyX3RlbXBlcmF0dXJlX2RhdGFfY2xlYW4gPC1jbGVhbl93ZWF0aGVyX2RhdGEod2VhdGhlcl90ZW1wZXJhdHVyZV9kYXRhKQ0Kd2VhdGhlcl90ZW1wZXJhdHVyZV9kYXRhX2NsZWFuICU+JSBwbG90X21pc3NpbmcoKQ0KYGBgDQpgYGB7cn0NCndlYXRoZXJfd2luZF9kaXJlY3Rpb25fZGF0YSAlPiUgcGxvdF9taXNzaW5nKCkNCmBgYA0KYGBge3J9DQp3ZWF0aGVyX3dpbmRfZGlyZWN0aW9uX2RhdGFfY2xlYW4gPC1jbGVhbl93ZWF0aGVyX2RhdGEod2VhdGhlcl93aW5kX2RpcmVjdGlvbl9kYXRhKQ0Kd2VhdGhlcl93aW5kX2RpcmVjdGlvbl9kYXRhX2NsZWFuICU+JSBwbG90X21pc3NpbmcoKQ0KYGBgDQpgYGB7cn0NCndlYXRoZXJfd2luZF9zcGVlZF9kYXRhICU+JSBwbG90X21pc3NpbmcoKQ0KYGBgDQpgYGB7cn0NCndlYXRoZXJfd2luZF9zcGVlZF9kYXRhX2NsZWFuIDwtY2xlYW5fd2VhdGhlcl9kYXRhKHdlYXRoZXJfd2luZF9zcGVlZF9kYXRhKQ0Kd2VhdGhlcl93aW5kX3NwZWVkX2RhdGFfY2xlYW4gJT4lIHBsb3RfbWlzc2luZygpDQpgYGANCmBgYHtyfQ0Kd2VhdGhlcl9odW1pZGl0eV9kYXRhICU+JSBwbG90X21pc3NpbmcoKQ0KYGBgDQpgYGB7cn0NCndlYXRoZXJfaHVtaWRpdHlfZGF0YV9jbGVhbiA8LWNsZWFuX3dlYXRoZXJfZGF0YSh3ZWF0aGVyX2h1bWlkaXR5X2RhdGEpDQp3ZWF0aGVyX2h1bWlkaXR5X2RhdGFfY2xlYW4gJT4lIHBsb3RfbWlzc2luZygpDQpgYGANCg0KYGBge3J9DQp3ZWF0aGVyX2h1bWlkaXR5X2RhdGFfY2xlYW4gPC0gd2VhdGhlcl9odW1pZGl0eV9kYXRhX2NsZWFuICU+JSAjIGNvbnZlcnQgdGltZSB0byB0aW1lIG9iamVjdA0KICAjbXV0YXRlKGRhdGVfdGltZSA9IHltZF9obXMod2VhdGhlcl9odW1pZGl0eV9kYXRhX2NsZWFuJFgpKQ0KICBtdXRhdGUoZGF0ZV90aW1lID0geW1kX2htcyh1bmxpc3QobWFwKHN0cnNwbGl0KHdlYXRoZXJfaHVtaWRpdHlfZGF0YV9jbGVhbiRYLCBzcGxpdD0nKycsIGZpeGVkPVRSVUUpLCAxKSkpKQ0KDQp3ZWF0aGVyX3RlbXBlcmF0dXJlX2RhdGFfY2xlYW4gPC0gd2VhdGhlcl90ZW1wZXJhdHVyZV9kYXRhX2NsZWFuICU+JSAjIGNvbnZlcnQgdGltZSB0byB0aW1lIG9iamVjdA0KICAjbXV0YXRlKGRhdGVfdGltZSA9IHltZF9obXMod2VhdGhlcl90ZW1wZXJhdHVyZV9kYXRhX2NsZWFuJFgpKQ0KICBtdXRhdGUoZGF0ZV90aW1lID0geW1kX2htcyh1bmxpc3QobWFwKHN0cnNwbGl0KHdlYXRoZXJfdGVtcGVyYXR1cmVfZGF0YV9jbGVhbiRYLCBzcGxpdD0nKycsIGZpeGVkPVRSVUUpLCAxKSkpKQ0KICANCndlYXRoZXJfd2luZF9kaXJlY3Rpb25fZGF0YV9jbGVhbiA8LSB3ZWF0aGVyX3dpbmRfZGlyZWN0aW9uX2RhdGFfY2xlYW4gJT4lICMgY29udmVydCB0aW1lIHRvIHRpbWUgb2JqZWN0DQogICNtdXRhdGUoZGF0ZV90aW1lID0geW1kX2htcyh3ZWF0aGVyX3dpbmRfZGlyZWN0aW9uX2RhdGFfY2xlYW4kWCkpDQogIG11dGF0ZShkYXRlX3RpbWUgPSB5bWRfaG1zKHVubGlzdChtYXAoc3Ryc3BsaXQod2VhdGhlcl93aW5kX2RpcmVjdGlvbl9kYXRhX2NsZWFuJFgsIHNwbGl0PScrJywgZml4ZWQ9VFJVRSksIDEpKSkpDQogIA0Kd2VhdGhlcl93aW5kX3NwZWVkX2RhdGFfY2xlYW4gPC0gd2VhdGhlcl93aW5kX3NwZWVkX2RhdGFfY2xlYW4gJT4lICMgY29udmVydCB0aW1lIHRvIHRpbWUgb2JqZWN0DQogICNtdXRhdGUoZGF0ZV90aW1lID0geW1kX2htcyh3ZWF0aGVyX3dpbmRfc3BlZWRfZGF0YV9jbGVhbiRYKSkNCiAgbXV0YXRlKGRhdGVfdGltZSA9IHltZF9obXModW5saXN0KG1hcChzdHJzcGxpdCh3ZWF0aGVyX3dpbmRfc3BlZWRfZGF0YV9jbGVhbiRYLCBzcGxpdD0nKycsIGZpeGVkPVRSVUUpLCAxKSkpKQ0KICANCndlYXRoZXJfcmFpbmZhbGxfZGF0YV9jbGVhbiA8LSB3ZWF0aGVyX3JhaW5mYWxsX2RhdGFfY2xlYW4gJT4lICMgY29udmVydCB0aW1lIHRvIHRpbWUgb2JqZWN0DQogICNtdXRhdGUoZGF0ZV90aW1lID0geW1kX2htcyh3ZWF0aGVyX3JhaW5mYWxsX2RhdGFfY2xlYW4kWCkpDQogIG11dGF0ZShkYXRlX3RpbWUgPSB5bWRfaG1zKHVubGlzdChtYXAoc3Ryc3BsaXQod2VhdGhlcl9yYWluZmFsbF9kYXRhX2NsZWFuJFgsIHNwbGl0PScrJywgZml4ZWQ9VFJVRSksIDEpKSkpDQogIA0KYGBgDQoNCg0KDQpgYGB7cn0NCmluc3BlY3Rfd2VhdGhlcl9kYXRhIDwtIGZ1bmN0aW9uKHdlYXRoZXJfZGF0YSwgdmFsdWVfbmFtZSl7DQogIHdlYXRoZXJfc3RhcnRfZGF0ZSA8LSBtaW4od2VhdGhlcl9kYXRhJGRhdGVfdGltZSkNCiAgd2VhdGhlcl9lbmRfZGF0ZSA8LSBtYXgod2VhdGhlcl9kYXRhJGRhdGVfdGltZSkNCiAgd2VhdGhlcl9kdXJhdGlvbiA8LSBtYXgod2VhdGhlcl9kYXRhJGRhdGVfdGltZSkgLSBtaW4od2VhdGhlcl9kYXRhJGRhdGVfdGltZSkNCiAgd2VhdGhlcl9mcmVxdWVuY3kgPC0gbnJvdyh3ZWF0aGVyX2RhdGEpIC8gYXMubnVtZXJpYyh3ZWF0aGVyX2R1cmF0aW9uKQ0KICBwcmludChwYXN0ZSgnRmlyc3QgZGF5IG9mIG1lYXN1cmVtZW50Jywgd2VhdGhlcl9zdGFydF9kYXRlKSkNCiAgcHJpbnQocGFzdGUoJ0xhc3QgZGF5IG9mIG1lYXN1cmVtZW50Jywgd2VhdGhlcl9lbmRfZGF0ZSkpDQogIHByaW50KHBhc3RlKCdEdXJhdGlvbiBvZiBtZWFzdXJlbWVudCBpbiBkYXlzJywgd2VhdGhlcl9kdXJhdGlvbikpDQogIHByaW50KHBhc3RlKCdGcmVxdWVuY3kgb2YgbWVhc3VyZW1lbnQgcGVyIGRheScsIHdlYXRoZXJfZnJlcXVlbmN5KSkNCiAgDQogIHdlYXRoZXJfcGxvdCA8LQ0KICAgIGdncGxvdChkYXRhID0gYXMuZGF0YS5mcmFtZShyZXNoYXBlMjo6bWVsdChzdWJzZXQod2VhdGhlcl9kYXRhLCBzZWxlY3QgPSAtYyhYKSksIGlkPSJkYXRlX3RpbWUiKSksIGFlcyh4ID0gZGF0ZV90aW1lLCB5ID0gdmFsdWUsIGNvbCA9IHZhcmlhYmxlKSkgKw0KICAgIGdlb21fbGluZSgpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIHRoZW1lKCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICsNCiAgICB4bGFiKCJEYXRlIikgKw0KICAgIHlsYWIodmFsdWVfbmFtZSkNCiAgDQogIHJldHVybih3ZWF0aGVyX3Bsb3QpDQp9DQpgYGANCg0KDQpgYGB7cn0NCmluc3BlY3Rfd2VhdGhlcl9kYXRhKHdlYXRoZXJfaHVtaWRpdHlfZGF0YV9jbGVhbiwgImh1bWlkaXR5ICUiKQ0KYGBgDQoNCmBgYHtyfQ0KaW5zcGVjdF93ZWF0aGVyX2RhdGEod2VhdGhlcl9yYWluZmFsbF9kYXRhX2NsZWFuLCAicmFpbmZhbGwgbW0iKQ0KYGBgDQoNCmBgYHtyfQ0KaW5zcGVjdF93ZWF0aGVyX2RhdGEod2VhdGhlcl90ZW1wZXJhdHVyZV9kYXRhX2NsZWFuLCAidGVtcGVyYXR1cmUgwrBDIikNCmBgYA0KYGBge3J9DQppbnNwZWN0X3dlYXRoZXJfZGF0YSh3ZWF0aGVyX3dpbmRfZGlyZWN0aW9uX2RhdGFfY2xlYW4sICJ3aW5kIGR1cmVjdGlvbiDCsCIpDQpgYGANCmBgYHtyfQ0KaW5zcGVjdF93ZWF0aGVyX2RhdGEod2VhdGhlcl93aW5kX3NwZWVkX2RhdGFfY2xlYW4sICJrbm90cyBtL3MiKQ0KYGBgDQoNCkhvdyBtYW55IHdlYXRoZXIgc3RhdGlvbnMgYXJlIHRoZXJlPw0KDQpgYGB7cn0NCnN0cih3ZWF0aGVyX3N0YXRpb25zX2RhdGEpDQpgYGANCg0KV2hhdCBhcmUgdGhlIHZhcmlhYmxlIG5hbWVzPw0KDQpgYGB7cn0NCm5hbWVzKHN1cnZleV9kYXRhKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VydmV5X2RhdGEgPC0gc3VydmV5X2RhdGEgJT4lIA0KICAjIGNvbnZlcnQgdGltZSB0byB0aW1lIG9iamVjdA0KICAjbXV0YXRlKGRhdGVfdGltZSA9IHltZF9obXMoc3VydmV5X2RhdGEkdGltZSkpICU+JSANCiAgbXV0YXRlKGRhdGVfdGltZSA9IHltZF9obXModW5saXN0KG1hcChzdHJzcGxpdChzdXJ2ZXlfZGF0YSR0aW1lLCBzcGxpdD0nLicsIGZpeGVkPVRSVUUpLCAxKSkpKSAlPiUgDQogICMgcmVwbGFjZSBlbXB0eSBzdHJpbmdzIHdpdGggTkENCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCB+bmFfaWYoLiwgIiIpKSkgDQpoZWFkKHN1cnZleV9kYXRhKQ0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0Kc3RyKHN1cnZleV9kYXRhKQ0KYGBgDQoNCkhvdyBtYW55IHVzZXJzIGFyZSBpbiB0aGUgc3R1ZHk/IEhvdyBtYW55IGRhdGEgcG9pbnRzIGZvciBlYWNoIHVzZXI/DQoNCmBgYHtyfQ0KbnJvdyh0YWJsZShzdXJ2ZXlfZGF0YSRpZF9wYXJ0aWNpcGFudCkpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3Qoc3VydmV5X2RhdGFbIWlzLm5hKHN1cnZleV9kYXRhJGlkX3BhcnRpY2lwYW50KSxdLGFlcyhpZF9wYXJ0aWNpcGFudCkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJjb3VudCIsIHBvc2l0aW9uID0gImRvZGdlIikgKw0KICB4bGFiKCJQYXJ0aWNpcGFudCBJbmRleCIpICsNCiAgeWxhYigiTnVtYmVyIG9mIFJlc3BvbnNlcyIpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHM9c2VxKDEsbnJvdyh0YWJsZShzdXJ2ZXlfZGF0YSRpZF9wYXJ0aWNpcGFudCkpKSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyANCiMgZ2dzYXZlKCJOdW1iZXJfb2ZfUmVzcG9uc2VzX3Blcl9wYXJ0aXNpcGFudC5wbmciLA0KIyAgICAgICAgd2lkdGggPSAxOCwgaGVpZ2h0ID0gNiwgZHBpID0gMjAwLCB1bml0cyA9ICJpbiIsIGRldmljZT0ncG5nJykNCmBgYA0KDQpgYGB7cn0NCmkxIDwtIHdoaWNoKCFpcy5uYShzdXJ2ZXlfZGF0YSRxX3RoZXJtYWxfcHJlZmVyZW5jZSkpDQoNCmxvZ3MgPC0gZGF0YS5mcmFtZShsb2dfcm93X2luZGV4ID0gcm93bmFtZXMoc3VydmV5X2RhdGFbaTEsXSksIA0KICAgICAgICAgICAgICAgICAgIGRhdGVfdGltZSA9IHN1cnZleV9kYXRhW2kxLF0kZGF0ZV90aW1lKQ0KYGBgDQoNCg0KYGBge3J9DQpncm91cGVkX2xvZ3MgPC0gbWVyZ2Uoc3VydmV5X2RhdGEsIGxvZ3MsIGJ5ID0gImRhdGVfdGltZSIsIGFsbC54ID0gVFJVRSkgJT4lDQogIGFycmFuZ2UoZGF0ZV90aW1lKSAlPiUgIyBzb3J0IGJ5IHRpbWUNCiAgZmlsbChsb2dfcm93X2luZGV4LCAuZGlyZWN0aW9uID0gImRvd24iKSAlPiUgIyBmaWxsIGdyb3VwIGluZGV4DQogIGZpbGwobG9nX3Jvd19pbmRleCwgLmRpcmVjdGlvbiA9ICJ1cCIpICU+JSAjIGZpbGwgZ3JvdXAgaW5kZXgNCiAgZ3JvdXBfYnkoaWRfcGFydGljaXBhbnQpICU+JSAjIGdyb3VwIGJ5IHBhcnRpY2lwYW50DQogIGFycmFuZ2UoaWRfcGFydGljaXBhbnQsIGRhdGVfdGltZSkgJT4lICNzb3J0IGJ5IHVzZXIgaWQgdGhlbiBieSB0aW1lDQogIGdyb3VwX2J5KGxvZ19yb3dfaW5kZXgpICMgZ3JvdXAgYnkgbG9nIGdyb3VwDQoNCm5yb3coZ3JvdXBlZF9sb2dzKQ0KYGBgDQoNCmBgYHtyfQ0KYXZnX2hlYXJ0X3JhdGVfcGFzdF8xMCA8LSBncm91cGVkX2xvZ3MgJT4lICMgZ3JvdXAgYnkgbG9nIGludGVydmFsDQogIHN1bW1hcml6ZShhdmVyYWdlX2hlYXJ0X3JhdGUgPSBtZWFuKHRhaWwobmEub21pdCh0c19oZWFydF9yYXRlKSksIG4gPSAxMCkpDQoNCm5yb3coYXZnX2hlYXJ0X3JhdGVfcGFzdF8xMCkNCmBgYA0KDQpgYGB7cn0NCnRvdGFsX2Rpc3RfcGFzdF8xMCA8LSBncm91cGVkX2xvZ3MgJT4lICMgZ3JvdXAgYnkgbG9nIGludGVydmFsDQogIHN1bW1hcml6ZShkaXN0X3dhbGtlZCA9IHN1bSh0YWlsKG5hLm9taXQodHNfd2Fsa2luZ19kaXN0YW5jZSkpLCBuID0gMTApKSANCiAgIyB0YWtlIG1lYW4gb2YgbGFzdCAxMCBub24gTmFOIHdhbGtlZCBkaXN0YW5jZSBtZWFzdXJlcw0KbnJvdyh0b3RhbF9kaXN0X3Bhc3RfMTApDQpgYGANCg0KYGBge3J9DQphY3Rpdml0eV9kYXRhIDwtIGxlZnRfam9pbihhdmdfaGVhcnRfcmF0ZV9wYXN0XzEwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2Rpc3RfcGFzdF8xMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJsb2dfcm93X2luZGV4IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlZXAgPSBGQUxTRSkNCmhlYWQoYWN0aXZpdHlfZGF0YSkNCmBgYA0KDQpgYGB7cn0NCmFjdGl2aXR5X2RhdGFfZnVsbCA8LSBzdXJ2ZXlfZGF0YVthY3Rpdml0eV9kYXRhJGxvZ19yb3dfaW5kZXgsXQ0KYWN0aXZpdHlfZGF0YV9mdWxsJGxvZ19yb3dfaW5kZXggPC0gYWN0aXZpdHlfZGF0YSRsb2dfcm93X2luZGV4DQphY3Rpdml0eV9kYXRhX2Z1bGwgPC0gbGVmdF9qb2luKGFjdGl2aXR5X2RhdGEsIGFjdGl2aXR5X2RhdGFfZnVsbCwgYnkgPSAibG9nX3Jvd19pbmRleCIpDQpoZWFkKGFjdGl2aXR5X2RhdGFfZnVsbCkNCmBgYA0KDQpgYGB7cn0NCmh1bWlkaXR5X3N0YXRpb25zX2lkcyA8LSBjb2xuYW1lcyh3ZWF0aGVyX2h1bWlkaXR5X2RhdGFfY2xlYW4pW2NvbG5hbWVzKHdlYXRoZXJfaHVtaWRpdHlfZGF0YV9jbGVhbikgJWluJSB3ZWF0aGVyX3N0YXRpb25zX2RhdGEkaWRdDQpodW1pZGl0eV9zdGF0aW9ucyA8LSB3ZWF0aGVyX3N0YXRpb25zX2RhdGFbd2VhdGhlcl9zdGF0aW9uc19kYXRhJGlkICVpbiUgaHVtaWRpdHlfc3RhdGlvbnNfaWRzLCBdDQoNCmQgPC0gcG9pbnREaXN0YW5jZShhY3Rpdml0eV9kYXRhX2Z1bGxbLGMoIndzX2xvbmdpdHVkZSIsICJ3c19sYXRpdHVkZSIpXSwgDQogICAgICAgICAgICAgICAgICAgaHVtaWRpdHlfc3RhdGlvbnNbLGMoImxvbmdpdHVkZSIsICJsYXRpdHVkZSIpXSwgDQogICAgICAgICAgICAgICAgICAgbG9ubGF0PVRSVUUsIGFsbHBhaXJzPVQpIA0KaSA8LSBhcHBseShkLCAxLCB3aGljaC5taW4pDQoNCmFjdGl2aXR5X2RhdGFfZnVsbCRodW1pZGl0eV9JRCA9IGh1bWlkaXR5X3N0YXRpb25zJGlkW2ldDQoNCg0KcmFpbmZhbGxfc3RhdGlvbnNfaWRzIDwtIGNvbG5hbWVzKHdlYXRoZXJfcmFpbmZhbGxfZGF0YV9jbGVhbilbY29sbmFtZXMod2VhdGhlcl9yYWluZmFsbF9kYXRhX2NsZWFuKSAlaW4lIHdlYXRoZXJfc3RhdGlvbnNfZGF0YSRpZF0NCnJhaW5mYWxsX3N0YXRpb25zIDwtIHdlYXRoZXJfc3RhdGlvbnNfZGF0YVt3ZWF0aGVyX3N0YXRpb25zX2RhdGEkaWQgJWluJSByYWluZmFsbF9zdGF0aW9uc19pZHMsIF0NCg0KZCA8LSBwb2ludERpc3RhbmNlKGFjdGl2aXR5X2RhdGFfZnVsbFssYygid3NfbG9uZ2l0dWRlIiwgIndzX2xhdGl0dWRlIildLCANCiAgICAgICAgICAgICAgICAgICByYWluZmFsbF9zdGF0aW9uc1ssYygibG9uZ2l0dWRlIiwgImxhdGl0dWRlIildLCANCiAgICAgICAgICAgICAgICAgICBsb25sYXQ9VFJVRSwgYWxscGFpcnM9VCkgDQppIDwtIGFwcGx5KGQsIDEsIHdoaWNoLm1pbikNCg0KYWN0aXZpdHlfZGF0YV9mdWxsJHJhaW5mYWxsX0lEID0gcmFpbmZhbGxfc3RhdGlvbnMkaWRbaV0NCg0KDQp0ZW1wZXJhdHVyZV9zdGF0aW9uc19pZHMgPC0gY29sbmFtZXMod2VhdGhlcl90ZW1wZXJhdHVyZV9kYXRhX2NsZWFuKVtjb2xuYW1lcyh3ZWF0aGVyX3RlbXBlcmF0dXJlX2RhdGFfY2xlYW4pICVpbiUgd2VhdGhlcl9zdGF0aW9uc19kYXRhJGlkXQ0KdGVtcGVyYXR1cmVfc3RhdGlvbnMgPC0gd2VhdGhlcl9zdGF0aW9uc19kYXRhW3dlYXRoZXJfc3RhdGlvbnNfZGF0YSRpZCAlaW4lIHRlbXBlcmF0dXJlX3N0YXRpb25zX2lkcywgXQ0KDQpkIDwtIHBvaW50RGlzdGFuY2UoYWN0aXZpdHlfZGF0YV9mdWxsWyxjKCJ3c19sb25naXR1ZGUiLCAid3NfbGF0aXR1ZGUiKV0sIA0KICAgICAgICAgICAgICAgICAgIHRlbXBlcmF0dXJlX3N0YXRpb25zWyxjKCJsb25naXR1ZGUiLCAibGF0aXR1ZGUiKV0sIA0KICAgICAgICAgICAgICAgICAgIGxvbmxhdD1UUlVFLCBhbGxwYWlycz1UKSANCmkgPC0gYXBwbHkoZCwgMSwgd2hpY2gubWluKQ0KDQphY3Rpdml0eV9kYXRhX2Z1bGwkdGVtcGVyYXR1cmVfSUQgPSB0ZW1wZXJhdHVyZV9zdGF0aW9ucyRpZFtpXQ0KDQoNCndpbmRfc3BlZWRfc3RhdGlvbnNfaWRzIDwtIGNvbG5hbWVzKHdlYXRoZXJfd2luZF9zcGVlZF9kYXRhX2NsZWFuKVtjb2xuYW1lcyh3ZWF0aGVyX3dpbmRfc3BlZWRfZGF0YV9jbGVhbikgJWluJSB3ZWF0aGVyX3N0YXRpb25zX2RhdGEkaWRdDQp3aW5kX3NwZWVkX3N0YXRpb25zIDwtIHdlYXRoZXJfc3RhdGlvbnNfZGF0YVt3ZWF0aGVyX3N0YXRpb25zX2RhdGEkaWQgJWluJSB3aW5kX3NwZWVkX3N0YXRpb25zX2lkcywgXQ0KDQpkIDwtIHBvaW50RGlzdGFuY2UoYWN0aXZpdHlfZGF0YV9mdWxsWyxjKCJ3c19sb25naXR1ZGUiLCAid3NfbGF0aXR1ZGUiKV0sIA0KICAgICAgICAgICAgICAgICAgIHdpbmRfc3BlZWRfc3RhdGlvbnNbLGMoImxvbmdpdHVkZSIsICJsYXRpdHVkZSIpXSwgDQogICAgICAgICAgICAgICAgICAgbG9ubGF0PVRSVUUsIGFsbHBhaXJzPVQpIA0KaSA8LSBhcHBseShkLCAxLCB3aGljaC5taW4pDQoNCmFjdGl2aXR5X2RhdGFfZnVsbCR3aW5kX3NwZWVkX0lEID0gd2luZF9zcGVlZF9zdGF0aW9ucyRpZFtpXQ0KDQoNCndpbmRfZGlyZWN0aW9uX3N0YXRpb25zX2lkcyA8LSBjb2xuYW1lcyh3ZWF0aGVyX3dpbmRfZGlyZWN0aW9uX2RhdGFfY2xlYW4pW2NvbG5hbWVzKHdlYXRoZXJfd2luZF9kaXJlY3Rpb25fZGF0YV9jbGVhbikgJWluJSB3ZWF0aGVyX3N0YXRpb25zX2RhdGEkaWRdDQp3aW5kX2RpcmVjdGlvbl9zdGF0aW9ucyA8LSB3ZWF0aGVyX3N0YXRpb25zX2RhdGFbd2VhdGhlcl9zdGF0aW9uc19kYXRhJGlkICVpbiUgd2luZF9kaXJlY3Rpb25fc3RhdGlvbnNfaWRzLCBdDQoNCmQgPC0gcG9pbnREaXN0YW5jZShhY3Rpdml0eV9kYXRhX2Z1bGxbLGMoIndzX2xvbmdpdHVkZSIsICJ3c19sYXRpdHVkZSIpXSwgDQogICAgICAgICAgICAgICAgICAgd2luZF9kaXJlY3Rpb25fc3RhdGlvbnNbLGMoImxvbmdpdHVkZSIsICJsYXRpdHVkZSIpXSwgDQogICAgICAgICAgICAgICAgICAgbG9ubGF0PVRSVUUsIGFsbHBhaXJzPVQpIA0KaSA8LSBhcHBseShkLCAxLCB3aGljaC5taW4pDQoNCmFjdGl2aXR5X2RhdGFfZnVsbCR3aW5kX2RpcmVjdGlvbl9JRCA9IHdpbmRfZGlyZWN0aW9uX3N0YXRpb25zJGlkW2ldDQoNCmhlYWQoYWN0aXZpdHlfZGF0YV9mdWxsKQ0KYGBgDQpgYGB7cn0NCnN0cih3ZWF0aGVyX2h1bWlkaXR5X2RhdGFfY2xlYW5bLGMoaHVtaWRpdHlfc3RhdGlvbnNfaWRzLCdkYXRlX3RpbWUnKV0pDQpgYGANCg0KDQoNCmBgYHtyfQ0KbWVsdGVkX2h1bWlkaXR5X2RhdGEgPC0gcmVzaGFwZTI6Om1lbHQod2VhdGhlcl9odW1pZGl0eV9kYXRhX2NsZWFuWyxjKGh1bWlkaXR5X3N0YXRpb25zX2lkcywnZGF0ZV90aW1lJyldLCBpZD0nZGF0ZV90aW1lJykNCmNvbG5hbWVzKG1lbHRlZF9odW1pZGl0eV9kYXRhKVtjb2xuYW1lcyhtZWx0ZWRfaHVtaWRpdHlfZGF0YSkgPT0gJ3ZhcmlhYmxlJ10gPC0gJ2h1bWlkaXR5X0lEJw0KY29sbmFtZXMobWVsdGVkX2h1bWlkaXR5X2RhdGEpW2NvbG5hbWVzKG1lbHRlZF9odW1pZGl0eV9kYXRhKSA9PSAndmFsdWUnXSA8LSAnaHVtaWRpdHknDQpzZXREVChtZWx0ZWRfaHVtaWRpdHlfZGF0YSkNCg0KbWVsdGVkX3JhaW5mYWxsX2RhdGEgPC0gcmVzaGFwZTI6Om1lbHQod2VhdGhlcl9yYWluZmFsbF9kYXRhX2NsZWFuWyxjKHJhaW5mYWxsX3N0YXRpb25zX2lkcywnZGF0ZV90aW1lJyldLCBpZD0nZGF0ZV90aW1lJykNCmNvbG5hbWVzKG1lbHRlZF9yYWluZmFsbF9kYXRhKVtjb2xuYW1lcyhtZWx0ZWRfcmFpbmZhbGxfZGF0YSkgPT0gJ3ZhcmlhYmxlJ10gPC0gJ3JhaW5mYWxsX0lEJw0KY29sbmFtZXMobWVsdGVkX3JhaW5mYWxsX2RhdGEpW2NvbG5hbWVzKG1lbHRlZF9yYWluZmFsbF9kYXRhKSA9PSAndmFsdWUnXSA8LSAncmFpbmZhbGwnDQpzZXREVChtZWx0ZWRfcmFpbmZhbGxfZGF0YSkNCg0KbWVsdGVkX3RlbXBlcmF0dXJlX2RhdGEgPC0gcmVzaGFwZTI6Om1lbHQod2VhdGhlcl90ZW1wZXJhdHVyZV9kYXRhX2NsZWFuWyxjKHRlbXBlcmF0dXJlX3N0YXRpb25zX2lkcywnZGF0ZV90aW1lJyldLCBpZD0nZGF0ZV90aW1lJykNCmNvbG5hbWVzKG1lbHRlZF90ZW1wZXJhdHVyZV9kYXRhKVtjb2xuYW1lcyhtZWx0ZWRfdGVtcGVyYXR1cmVfZGF0YSkgPT0gJ3ZhcmlhYmxlJ10gPC0gJ3RlbXBlcmF0dXJlX0lEJw0KY29sbmFtZXMobWVsdGVkX3RlbXBlcmF0dXJlX2RhdGEpW2NvbG5hbWVzKG1lbHRlZF90ZW1wZXJhdHVyZV9kYXRhKSA9PSAndmFsdWUnXSA8LSAndGVtcGVyYXR1cmUnDQpzZXREVChtZWx0ZWRfdGVtcGVyYXR1cmVfZGF0YSkNCg0KbWVsdGVkX3dpbmRfc3BlZWRfZGF0YSA8LSByZXNoYXBlMjo6bWVsdCh3ZWF0aGVyX3dpbmRfc3BlZWRfZGF0YV9jbGVhblssYyh3aW5kX3NwZWVkX3N0YXRpb25zX2lkcywnZGF0ZV90aW1lJyldLCBpZD0nZGF0ZV90aW1lJykNCmNvbG5hbWVzKG1lbHRlZF93aW5kX3NwZWVkX2RhdGEpW2NvbG5hbWVzKG1lbHRlZF93aW5kX3NwZWVkX2RhdGEpID09ICd2YXJpYWJsZSddIDwtICd3aW5kX3NwZWVkX0lEJw0KY29sbmFtZXMobWVsdGVkX3dpbmRfc3BlZWRfZGF0YSlbY29sbmFtZXMobWVsdGVkX3dpbmRfc3BlZWRfZGF0YSkgPT0gJ3ZhbHVlJ10gPC0gJ3dpbmRfc3BlZWQnDQpzZXREVChtZWx0ZWRfd2luZF9zcGVlZF9kYXRhKQ0KDQptZWx0ZWRfd2luZF9kaXJlY3Rpb25fZGF0YSA8LSByZXNoYXBlMjo6bWVsdCh3ZWF0aGVyX3dpbmRfZGlyZWN0aW9uX2RhdGFfY2xlYW5bLGMod2luZF9kaXJlY3Rpb25fc3RhdGlvbnNfaWRzLCdkYXRlX3RpbWUnKV0sIGlkPSdkYXRlX3RpbWUnKQ0KY29sbmFtZXMobWVsdGVkX3dpbmRfZGlyZWN0aW9uX2RhdGEpW2NvbG5hbWVzKG1lbHRlZF93aW5kX2RpcmVjdGlvbl9kYXRhKSA9PSAndmFyaWFibGUnXSA8LSAnd2luZF9kaXJlY3Rpb25fSUQnDQpjb2xuYW1lcyhtZWx0ZWRfd2luZF9kaXJlY3Rpb25fZGF0YSlbY29sbmFtZXMobWVsdGVkX3dpbmRfZGlyZWN0aW9uX2RhdGEpID09ICd2YWx1ZSddIDwtICd3aW5kX2RpcmVjdGlvbicNCnNldERUKG1lbHRlZF93aW5kX2RpcmVjdGlvbl9kYXRhKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0RFQoYWN0aXZpdHlfZGF0YV9mdWxsKQ0KDQphY3Rpdml0eV9kYXRhX2Z1bGwgPC0gYWN0aXZpdHlfZGF0YV9mdWxsWywgYygiaHVtaWRpdHlUaW1lIiwgImh1bWlkaXR5IikgOj0gDQogICAgbWVsdGVkX2h1bWlkaXR5X2RhdGFbYWN0aXZpdHlfZGF0YV9mdWxsLCBvbiA9IGMoImh1bWlkaXR5X0lEIiwgImRhdGVfdGltZSIpLCByb2xsID0gSW5mLCAuKHguZGF0ZV90aW1lLCB4Lmh1bWlkaXR5KV1dW10NCg0KYWN0aXZpdHlfZGF0YV9mdWxsIDwtIGFjdGl2aXR5X2RhdGFfZnVsbFssIGMoInJhaW5mYWxsVGltZSIsICJyYWluZmFsbCIpIDo9IA0KICAgIG1lbHRlZF9yYWluZmFsbF9kYXRhW2FjdGl2aXR5X2RhdGFfZnVsbCwgb24gPSBjKCJyYWluZmFsbF9JRCIsICJkYXRlX3RpbWUiKSwgcm9sbCA9IEluZiwgLih4LmRhdGVfdGltZSwgeC5yYWluZmFsbCldXVtdDQoNCmFjdGl2aXR5X2RhdGFfZnVsbCA8LSBhY3Rpdml0eV9kYXRhX2Z1bGxbLCBjKCJ0ZW1wZXJhdHVyZVRpbWUiLCAidGVtcGVyYXR1cmUiKSA6PSANCiAgICBtZWx0ZWRfdGVtcGVyYXR1cmVfZGF0YVthY3Rpdml0eV9kYXRhX2Z1bGwsIG9uID0gYygidGVtcGVyYXR1cmVfSUQiLCAiZGF0ZV90aW1lIiksIHJvbGwgPSBJbmYsIC4oeC5kYXRlX3RpbWUsIHgudGVtcGVyYXR1cmUpXV1bXQ0KDQphY3Rpdml0eV9kYXRhX2Z1bGwgPC0gYWN0aXZpdHlfZGF0YV9mdWxsWywgYygid2luZF9zcGVlZFRpbWUiLCAid2luZF9zcGVlZCIpIDo9IA0KICAgIG1lbHRlZF93aW5kX3NwZWVkX2RhdGFbYWN0aXZpdHlfZGF0YV9mdWxsLCBvbiA9IGMoIndpbmRfc3BlZWRfSUQiLCAiZGF0ZV90aW1lIiksIHJvbGwgPSBJbmYsIC4oeC5kYXRlX3RpbWUsIHgud2luZF9zcGVlZCldXVtdDQoNCmFjdGl2aXR5X2RhdGFfZnVsbCA8LSBhY3Rpdml0eV9kYXRhX2Z1bGxbLCBjKCJ3aW5kX2RpcmVjdGlvblRpbWUiLCAid2luZF9kaXJlY3Rpb24iKSA6PSANCiAgICBtZWx0ZWRfd2luZF9kaXJlY3Rpb25fZGF0YVthY3Rpdml0eV9kYXRhX2Z1bGwsIG9uID0gYygid2luZF9kaXJlY3Rpb25fSUQiLCAiZGF0ZV90aW1lIiksIHJvbGwgPSBJbmYsIC4oeC5kYXRlX3RpbWUsIHgud2luZF9kaXJlY3Rpb24pXV1bXQ0KDQpoZWFkKGFjdGl2aXR5X2RhdGFfZnVsbCwgMTAwKQ0KYGBgDQoNCmBgYHtyfQ0Kc2VsZWN0ZWRfZGF0YSA8LSBhY3Rpdml0eV9kYXRhX2Z1bGxbLGMoDQogICdpZF9wYXJ0aWNpcGFudCcsDQogICd3c19sb25naXR1ZGUnLA0KICAnd3NfbGF0aXR1ZGUnLA0KICAnZGlzdF93YWxrZWQnLA0KICAnYXZlcmFnZV9oZWFydF9yYXRlJywNCiAgJ3FfbG9jYXRpb24nLA0KICAnR3JlZW4uVmlldy5NZWFuJywNCiAgJ0Zvb3RwcmludC5NZWFuJywNCiAgJ1BlcmltZXRlci5NZWFuJywNCiAgJ0J1aWxkaW5nLkNvdW50JywNCiAgJ1NreS5WaWV3Lk1lYW4nLA0KICAnQnVpbGRpbmcuVmlldy5NZWFuJywNCiAgJ1JvYWQuVmlldy5NZWFuJywNCiAgJ2h1bWlkaXR5JywNCiAgJ3JhaW5mYWxsJywNCiAgJ3RlbXBlcmF0dXJlJywNCiAgJ3dpbmRfc3BlZWQnLA0KICAnd2luZF9kaXJlY3Rpb24nLA0KICAncV90aGVybWFsX3ByZWZlcmVuY2UnLA0KICAnZGF0ZV90aW1lJywNCiAgJ2RUJywNCiAgJ1Zpc3VhbC5Db21wbGV4aXR5Lk1lYW4nDQopXQ0KDQpzZWxlY3RlZF9kYXRhIDwtIGRyb3BfbmEoc2VsZWN0ZWRfZGF0YSkNCg0KDQpzZWxlY3RlZF9kYXRhJHFfbG9jYXRpb24gPC0gYXMuZmFjdG9yKHNlbGVjdGVkX2RhdGEkcV9sb2NhdGlvbikNCnNlbGVjdGVkX2RhdGEkcV90aGVybWFsX3ByZWZlcmVuY2UgPC0NCiAgYXMuZmFjdG9yKHNlbGVjdGVkX2RhdGEkcV90aGVybWFsX3ByZWZlcmVuY2UpDQpgYGANCg0KDQpgYGB7cn0NCnN1bW1hcnkoc2VsZWN0ZWRfZGF0YSkNCmBgYA0KDQpgYGB7cn0NCnNlbGVjdGVkX2RhdGEgJT4lIHBsb3RfaGlzdG9ncmFtKGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkpIA0KYGBgDQoNCmBgYHtyfQ0Kc2VsZWN0ZWRfZGF0YV9ub19vdXRsaWVycyA8LSBzZWxlY3RlZF9kYXRhWygNCiAgc2VsZWN0ZWRfZGF0YSRkVCA8IDUwMCAmICMgcmVtb3ZlIG1lYXN1cmVtZW50cyB0aGF0IGFyZSBpbmZyZXF1ZW50DQogIHNlbGVjdGVkX2RhdGEkRm9vdHByaW50Lk1lYW4gPCAxMDAwMA0KICAgKSxdDQoNCg0KI3NlbGVjdGVkX2RhdGFfbm9fb3V0bGllcnMgPC0gc2VsZWN0ZWRfZGF0YV9ub19vdXRsaWVyc1tzZWxlY3RlZF9kYXRhX25vX291dGxpZXJzJHFfbG9jYXRpb24gPT0gIk91dGRvb3IiLCBdDQpzZWxlY3RlZF9kYXRhX25vX291dGxpZXJzICU+JSBwbG90X2hpc3RvZ3JhbShnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpKSANCmBgYA0KYGBge3J9DQpnZ3Bsb3QoZGF0YT1zZWxlY3RlZF9kYXRhX25vX291dGxpZXJzLGFlcyhxX3RoZXJtYWxfcHJlZmVyZW5jZSkpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGw9YXMuZmFjdG9yKHJvdW5kKHRlbXBlcmF0dXJlKSkpKSArIHRoZW1lX21pbmltYWwoKSArIA0KICBzY2FsZV9jb2xvcl9ncmFkaWVudDIobG93ID0gImJsdWUiLCBtaWQgPSAid2hpdGUiLCBoaWdoID0gInJlZCIsIHNwYWNlID0gIkxhYiIgKQ0KYGBgDQoNCg0KYGBge3J9DQojIGNyZWF0aW5nIGEgc2FtcGxlIGRhdGEuZnJhbWUgd2l0aCB5b3VyIGxhdC9sb24gcG9pbnRzDQpsb24gPC0gc2VsZWN0ZWRfZGF0YV9ub19vdXRsaWVycyR3c19sb25naXR1ZGUNCmxhdCA8LSBzZWxlY3RlZF9kYXRhX25vX291dGxpZXJzJHdzX2xhdGl0dWRlDQp0aGVybWFsX3ByZWZlcmVuY2UgPC0gc2VsZWN0ZWRfZGF0YV9ub19vdXRsaWVycyRxX3RoZXJtYWxfcHJlZmVyZW5jZQ0KDQpkZiA8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKGxvbixsYXQpKQ0KDQpkZiA8LSBkZiAlPiUgDQogIGFycmFuZ2UodGhlcm1hbF9wcmVmZXJlbmNlKSAlPiUgDQogIG11dGF0ZSh0aGVybWFsX3ByZWZlcmVuY2UgPSBhcy5jaGFyYWN0ZXIodGhlcm1hbF9wcmVmZXJlbmNlKSwNCiAgICAgICAgIGNvbG9yID0gcmVjb2RlKHRoZXJtYWxfcHJlZmVyZW5jZSwnQ29vbGVyJyA9ICJyZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIk5vIGNoYW5nZSIgPSAiZ3JlZW4iLCAiV2FybWVyIiA9ICJibHVlIikpDQoNCmZpZyA8LSBkZg0KDQpmaWcgPC0gZmlnICU+JQ0KICBwbG90X2x5KA0KICAgIGxhdCA9IH5sYXQsDQogICAgbG9uID0gfmxvbiwNCiAgICB0eXBlID0gJ3NjYXR0ZXJtYXBib3gnLA0KICAgIG1vZGUgPSAibWFya2VycyIsDQogICAgc3ltYm9sICA9IH50aGVybWFsX3ByZWZlcmVuY2UsDQogICAgbWFya2VyID0gbGlzdChjb2xvciA9IH5jb2xvcixzaXplPTUpKSANCg0KDQpmaWcgPC0gZmlnICU+JQ0KICBsYXlvdXQoDQogICAgbWFwYm94ID0gbGlzdCgNCiAgICAgIHN0eWxlID0gJ29wZW4tc3RyZWV0LW1hcCcsDQogICAgICB6b29tID05LjUsDQogICAgICBjZW50ZXIgPSBsaXN0KGxvbiA9IG1lYW4oZGYkbG9uKSwgbGF0ID0gbWVhbihkZiRsYXQpKSkpDQoNCnBiIDwtIHBsb3RseV9idWlsZChmaWcpDQoNCnBiDQoNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChzZWxlY3RlZF9kYXRhX25vX291dGxpZXJzLCBhZXMoZGF0ZV90aW1lLCB0ZW1wZXJhdHVyZSkpICsgZ2VvbV9wb2ludCgpICsgdGhlbWVfbWluaW1hbCgpDQoNCmBgYA0KDQpgYGB7cn0NCmFzLm51bWVyaWMoc3VydmV5X2RhdGEkZGF0ZV90aW1lKSAlPiUNCiAgcGxvdF9oaXN0b2dyYW0oZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSkNCmBgYA0KDQpgYGB7cn0NCiMgVGVtcGVyYXR1cmUgaW5jcmVhc2VzIGR1cmluZyB0aGUgZGF5DQpnZ3Bsb3Qoc2VsZWN0ZWRfZGF0YVsgc2VsZWN0ZWRfZGF0YSRkYXRlX3RpbWUgPiB5bWQoIjIwMjIvMTAvMTgiKSAmDQogICAgICAgICAgICAgICAgICAgICAgc2VsZWN0ZWRfZGF0YSRkYXRlX3RpbWUgPCB5bWQoIjIwMjIvMTAvMTkiKSAjJg0KICAgICAgICAgICAgICAgICAgICAgLF0sIGFlcyhkYXRlX3RpbWUsIHRlbXBlcmF0dXJlKSkgKyBnZW9tX3BvaW50KCkgKyB0aGVtZV9taW5pbWFsKCkNCmBgYA0KPT09PT09PT09PT09PT09PT09PT09PT09TW9kZWwgVGVzdGluZyBTdGFydHMgSGVyZT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KYGBge3J9DQpzZWxlY3RlZF9kYXRhX2xvZyA8LQ0KICBzdWJzZXQoDQogICAgc2VsZWN0ZWRfZGF0YV9ub19vdXRsaWVycywNCiAgICBzZWxlY3QgPSAtYygNCiAgICAgIGlkX3BhcnRpY2lwYW50LA0KICAgICAgd3NfbG9uZ2l0dWRlLA0KICAgICAgd3NfbGF0aXR1ZGUsDQogICAgICBQZXJpbWV0ZXIuTWVhbiwNCiAgICAgIGh1bWlkaXR5LA0KICAgICAgd2luZF9kaXJlY3Rpb24sDQogICAgICBCdWlsZGluZy5Db3VudCwNCiAgICAgIGRUDQogICAgKQ0KICApDQoNCg0Kc2VsZWN0ZWRfZGF0YV9sb2ckcV90aGVybWFsX3ByZWZlcmVuY2UgPC0NCiAgYXMuZmFjdG9yKHNlbGVjdGVkX2RhdGFfbG9nJHFfdGhlcm1hbF9wcmVmZXJlbmNlID09ICJDb29sZXIiKQ0KDQpzZWxlY3RlZF9kYXRhX2xvZyRpc19vdXRkb29yIDwtICANCiAgYXMuZmFjdG9yKHNlbGVjdGVkX2RhdGFfbG9nJHFfbG9jYXRpb24gPT0gIk91dGRvb3IiKQ0KDQojIA0Kc2VsZWN0ZWRfZGF0YV9sb2ckaXNfd2ludGVyIDwtIGFzLmZhY3RvcihzZWxlY3RlZF9kYXRhX2xvZyRkYXRlX3RpbWUgPiB5bSgiMjAyMy8wNCIpKQ0Kc2VsZWN0ZWRfZGF0YV9sb2ckaXNfZGF5IDwtIGFzLmZhY3RvcigoaG91cihzZWxlY3RlZF9kYXRhX2xvZyRkYXRlX3RpbWUpID4gMTIgJg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihzZWxlY3RlZF9kYXRhX2xvZyRkYXRlX3RpbWUpIDwgMTgpID09IFQpDQoNCnNlbGVjdGVkX2RhdGFfbG9nIDwtDQogIHN1YnNldChzZWxlY3RlZF9kYXRhX2xvZywgc2VsZWN0ID0gLWMocV9sb2NhdGlvbiwgZGF0ZV90aW1lKSkNCg0Kc2V0LnNlZWQoMSkNCg0KIyBEaXZpZGUgdGhlIGRhdGEgaW50byA4MCUgdHJhaW5pbmcgYW5kIDIwJSB0ZXN0aW5nDQp0cmFpbiA8LQ0KICBzYW1wbGUoMTpucm93KHNlbGVjdGVkX2RhdGFfbG9nKSwNCiAgICAgICAgIHNpemUgPSByb3VuZChucm93KHNlbGVjdGVkX2RhdGFfbG9nKSAqIDAuOCksDQogICAgICAgICByZXBsYWNlID0gRkFMU0UpDQoNCnNlbGVjdGVkX2RhdGFfbG9nX3RyYWluIDwtIHNlbGVjdGVkX2RhdGFfbG9nW3RyYWluLCBdDQpzZWxlY3RlZF9kYXRhX2xvZ190ZXN0IDwtIHNlbGVjdGVkX2RhdGFfbG9nWy10cmFpbiwgXQ0KDQpzZWxlY3RlZF9kYXRhX2xvZyAlPiUNCiAgbXV0YXRlKGlzX291dGRvb3IgPSBhcy5udW1lcmljKGlzX291dGRvb3IpKSAlPiUNCiAgbXV0YXRlKHFfdGhlcm1hbF9wcmVmZXJlbmNlID0gYXMubnVtZXJpYyhxX3RoZXJtYWxfcHJlZmVyZW5jZSkpICU+JQ0KICBtdXRhdGUoaXNfd2ludGVyID0gYXMubnVtZXJpYyhpc193aW50ZXIpKSAlPiUNCiAgbXV0YXRlKGlzX2RheSA9IGFzLm51bWVyaWMoaXNfZGF5KSkgJT4lDQogIGNvcih1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikgJT4lDQogIGNvcnJwbG90KG9yZGVyID0gJ2FscGhhYmV0JywgZGlhZyA9IEYpDQpgYGANCmBgYHtyfQ0Kc3RyKHNlbGVjdGVkX2RhdGFfbG9nX3RyYWluKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCm1vZGVsIDwtIGdsbShxX3RoZXJtYWxfcHJlZmVyZW5jZSB+IC4gLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IiksIGRhdGEgPSBzZWxlY3RlZF9kYXRhX2xvZ190cmFpbikNCnN1bW1hcnkobW9kZWwpDQpgYGANCmBgYHtyfQ0KbW9kZWwyIDwtDQogIGdsbSgNCiAgICBxX3RoZXJtYWxfcHJlZmVyZW5jZSB+IGlzX3dpbnRlciArIGlzX291dGRvb3IgKyBpc19kYXkgKyB0ZW1wZXJhdHVyZSArIFNreS5WaWV3Lk1lYW4sDQogICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpLA0KICAgIGRhdGEgPSBzZWxlY3RlZF9kYXRhX2xvZ190cmFpbg0KICApDQpzdW1tYXJ5KG1vZGVsMikNCg0KYGBgDQoNCmBgYHtyfQ0KIyBTdGVwIDM6IFByZWRpY3QgcHJvYmFiaWxpdGllcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KG1vZGVsMiwgc2VsZWN0ZWRfZGF0YV9sb2dfdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCiMgU3RlcCA0IGFuZCA1OiBVc2UgZGlmZmVyZW50IGN1dG9mZnMgYW5kIGNhbGN1bGF0ZSBhY2N1cmFjeQ0KY3V0b2ZmcyA8LSBjKDAuMiwgMC4zLCAwLjQsIDAuNSwgMC42LCAwLjcsIDAuOCwgMC45KQ0KDQphY2N1cmFjaWVzIDwtIHNhcHBseShjdXRvZmZzLCBmdW5jdGlvbihjdXRvZmYpIHsNCiAgIyBDb252ZXJ0IHByb2JhYmlsaXRpZXMgdG8gYmluYXJ5IHByZWRpY3Rpb25zDQogIHByZWRpY3Rpb25zIDwtIGlmZWxzZShwcm9iYWJpbGl0aWVzID4gY3V0b2ZmLCAyLCAxKQ0KICANCiAgIyBDYWxjdWxhdGUgYWNjdXJhY3kNCiAgYWNjdXJhY3koYXMubnVtZXJpYyhzZWxlY3RlZF9kYXRhX2xvZ190ZXN0JHFfdGhlcm1hbF9wcmVmZXJlbmNlKSwgcHJlZGljdGlvbnMpDQogICNtZWFuKHByZWRpY3Rpb25zID09IGFzLm51bWVyaWMoc2VsZWN0ZWRfZGF0YV9sb2dfdGVzdCRxX3RoZXJtYWxfcHJlZmVyZW5jZSkpDQp9KQ0KDQojIFByaW50IHRoZSBhY2N1cmFjaWVzIGZvciBlYWNoIGN1dG9mZg0KbmFtZXMoYWNjdXJhY2llcykgPC0gY3V0b2Zmcw0KYWNjdXJhY2llcw0KcFIyKG1vZGVsMilbJ01jRmFkZGVuJ10NCmBgYA0KDQpgYGB7cn0NCmZpdC50cmVlID0gcnBhcnQocV90aGVybWFsX3ByZWZlcmVuY2UgfiAuLCBkYXRhPXNlbGVjdGVkX2RhdGFfbG9nX3RyYWluLCBtZXRob2Q9ImNsYXNzIiwgY3A9MC4wMDgpDQpwcnAoZml0LnRyZWUsDQogICAgbWFpbiA9ICJUcmVlIG1vZGVsIGZvciBwcmVkaWN0aW5nIGlmIHRoZXJtYWwgcHJlZmVyZW5jZSBpcyBcIkNvb2xlclwiIiwNCiAgICBib3gucGFsZXR0ZSA9ICJhdXRvIiwNCiAgICBmYWxsZW4ubGVhdmVzID0gRiwgIA0KICAgIHNoYWRvdy5jb2wgPSAiZ3JheSIsICAgDQogICAgYnJhbmNoLmx0eSA9IDMsICAgICAgICANCiAgICBicmFuY2ggPSAuNSwgICAgICAgICAgIA0KICAgIGZhY2xlbiA9IDAsICAgICAgICAgICAgDQogICAgcm91bmQgPSAwKQ0KYGBgDQoNCmBgYHtyfQ0KcHJpbnRjcChmaXQudHJlZSkNCmBgYA0KDQpgYGB7cn0NCmJlc3RjcCA8LSBmaXQudHJlZSRjcHRhYmxlW3doaWNoLm1pbihmaXQudHJlZSRjcHRhYmxlWywieGVycm9yIl0pLCJDUCJdDQpwcnVuZWQudHJlZSA8LSBwcnVuZShmaXQudHJlZSwgY3AgPSBiZXN0Y3ApDQoNCnBycChwcnVuZWQudHJlZSwNCiAgICBtYWluID0gIlRyZWUgbW9kZWwgZm9yIHByZWRpY3RpbmcgaWYgdGhlcm1hbCBwcmVmZXJlbmNlIGlzIFwiQ29vbGVyXCIiLA0KICAgIGJveC5wYWxldHRlID0gImF1dG8iLA0KICAgIGZhbGxlbi5sZWF2ZXMgPSBGLCAgDQogICAgc2hhZG93LmNvbCA9ICJncmF5IiwgICANCiAgICBicmFuY2gubHR5ID0gMywgICAgICAgIA0KICAgIGJyYW5jaCA9IC41LCAgICAgICAgICAgDQogICAgZmFjbGVuID0gMCwgICAgICAgICAgICANCiAgICByb3VuZCA9IDApDQpgYGANCmBgYHtyfQ0KcHJlZGljdGVkIDwtIGFzLm51bWVyaWMocHJlZGljdChwcnVuZWQudHJlZSwgc2VsZWN0ZWRfZGF0YV9sb2dfdGVzdCwgdHlwZSA9ICJjbGFzcyIpKQ0Kc3VtKHByZWRpY3RlZCA9PSBhcy5udW1lcmljKHNlbGVjdGVkX2RhdGFfbG9nX3Rlc3QkcV90aGVybWFsX3ByZWZlcmVuY2UpKSAvIG5yb3coc2VsZWN0ZWRfZGF0YV9sb2dfdGVzdCkNCk1ldHJpY3M6OmFjY3VyYWN5KGFzLm51bWVyaWMoc2VsZWN0ZWRfZGF0YV9sb2dfdGVzdCRxX3RoZXJtYWxfcHJlZmVyZW5jZSksIHByZWRpY3RlZCkNCmBgYA0KDQpgYGB7cn0NClNTX3RvdCA8LSBzdW0oKGFzLm51bWVyaWMoc2VsZWN0ZWRfZGF0YV9sb2dfdHJhaW4kcV90aGVybWFsX3ByZWZlcmVuY2UpIC0gbWVhbihhcy5udW1lcmljKHNlbGVjdGVkX2RhdGFfbG9nX3RyYWluJHFfdGhlcm1hbF9wcmVmZXJlbmNlKSkpIF4gMikNClNTX3Jlc190cmVlIDwtIHN1bSgoYXMubnVtZXJpYyhzZWxlY3RlZF9kYXRhX2xvZ190cmFpbiRxX3RoZXJtYWxfcHJlZmVyZW5jZSkgLSBhcy5udW1lcmljKHByZWRpY3QocHJ1bmVkLnRyZWUsIHNlbGVjdGVkX2RhdGFfbG9nX3RyYWluLCB0eXBlID0gImNsYXNzIikpKSBeIDIpDQoNClJfc3FfbG0gPC0gMSAtIFNTX3Jlc190cmVlIC8gU1NfdG90DQpSX3NxX2xtDQpgYGANCg0KYGBge3J9DQpzZXQuc2VlZCg1MCkNCg0KbW9kZWxfZm9yZXN0IDwtDQogIHJhbmRvbUZvcmVzdCgNCiAgICBxX3RoZXJtYWxfcHJlZmVyZW5jZSB+IC4gLA0KICAgIGRhdGEgPSBzZWxlY3RlZF9kYXRhX2xvZ190cmFpbiwNCiAgICBpbXBvcnRhbmNlID0gVFJVRSwNCiAgICBudHJlZSA9IDE1MA0KICApDQoNCnByZWRpY3RlZCA8LSBhcy5udW1lcmljKHByZWRpY3QobW9kZWxfZm9yZXN0LCBzZWxlY3RlZF9kYXRhX2xvZ190ZXN0KSkNCnN1bShwcmVkaWN0ZWQgPT0gYXMubnVtZXJpYyhzZWxlY3RlZF9kYXRhX2xvZ190ZXN0JHFfdGhlcm1hbF9wcmVmZXJlbmNlKSkgLyBucm93KHNlbGVjdGVkX2RhdGFfbG9nX3Rlc3QpDQpNZXRyaWNzOjphY2N1cmFjeShhcy5udW1lcmljKHNlbGVjdGVkX2RhdGFfbG9nX3Rlc3QkcV90aGVybWFsX3ByZWZlcmVuY2UpLCBwcmVkaWN0ZWQpDQpgYGANCmBgYHtyfQ0KcmFuZG9tRm9yZXN0OjppbXBvcnRhbmNlKG1vZGVsX2ZvcmVzdCkNCmBgYA0KDQoNCmBgYHtyfQ0KU1NfdG90IDwtIHN1bSgoYXMubnVtZXJpYyhzZWxlY3RlZF9kYXRhX2xvZ190cmFpbiRxX3RoZXJtYWxfcHJlZmVyZW5jZSkgLSBtZWFuKGFzLm51bWVyaWMoc2VsZWN0ZWRfZGF0YV9sb2dfdHJhaW4kcV90aGVybWFsX3ByZWZlcmVuY2UpKSkgXiAyKQ0KU1NfcmVzX3RyZWUgPC0gc3VtKChhcy5udW1lcmljKHNlbGVjdGVkX2RhdGFfbG9nX3RyYWluJHFfdGhlcm1hbF9wcmVmZXJlbmNlKSAtIGFzLm51bWVyaWMocHJlZGljdChtb2RlbF9mb3Jlc3QsIHNlbGVjdGVkX2RhdGFfbG9nX3RyYWluKSkpIF4gMikNCg0KUl9zcV9sbSA8LSAxIC0gU1NfcmVzX3RyZWUgLyBTU190b3QNClJfc3FfbG0NCmBgYA0KDQpgYGB7cn0NCnNlbGVjdGVkX2RhdGFfbXVsdGlub20gPC0NCiAgc3Vic2V0KA0KICAgIHNlbGVjdGVkX2RhdGFfbm9fb3V0bGllcnMsDQogICAgc2VsZWN0ID0gLWMoDQogICAgICBpZF9wYXJ0aWNpcGFudCwNCiAgICAgIHdzX2xvbmdpdHVkZSwNCiAgICAgIHdzX2xhdGl0dWRlLA0KICAgICAgUGVyaW1ldGVyLk1lYW4sDQogICAgICB3aW5kX2RpcmVjdGlvbiwNCiAgICAgIEJ1aWxkaW5nLkNvdW50LA0KICAgICAgZFQNCiAgICApDQogICkNCg0KDQpzZWxlY3RlZF9kYXRhX211bHRpbm9tJHFfdGhlcm1hbF9wcmVmZXJlbmNlIDwtDQogIGFzLmZhY3RvcihzZWxlY3RlZF9kYXRhX211bHRpbm9tJHFfdGhlcm1hbF9wcmVmZXJlbmNlKQ0KDQpzZWxlY3RlZF9kYXRhX211bHRpbm9tJGlzX291dGRvb3IgPC0gIA0KICBhcy5mYWN0b3Ioc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbSRxX2xvY2F0aW9uID09ICJPdXRkb29yIikNCg0Kc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbSRpc193aW50ZXIgPC0gYXMuZmFjdG9yKHNlbGVjdGVkX2RhdGFfbXVsdGlub20kZGF0ZV90aW1lID4geW0oIjIwMjMvMDQiKSkNCnNlbGVjdGVkX2RhdGFfbXVsdGlub20kaXNfZGF5IDwtICBhcy5mYWN0b3IoKGhvdXIoc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbSRkYXRlX3RpbWUpID4gMTIgJg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihzZWxlY3RlZF9kYXRhX211bHRpbm9tJGRhdGVfdGltZSkgPCAxOCkgPT0gVCkNCg0Kc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbSA8LQ0KICBzdWJzZXQoc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbSwgc2VsZWN0ID0gLWMocV9sb2NhdGlvbiwgZGF0ZV90aW1lKSkNCg0Kc2V0LnNlZWQoMikNCg0KIyBEaXZpZGUgdGhlIGRhdGEgaW50byA4MCUgdHJhaW5pbmcgYW5kIDIwJSB0ZXN0aW5nDQp0cmFpbiA8LQ0KICBzYW1wbGUoMTpucm93KHNlbGVjdGVkX2RhdGFfbXVsdGlub20pLA0KICAgICAgICAgc2l6ZSA9IHJvdW5kKG5yb3coc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbSkgKiAwLjgpLA0KICAgICAgICAgcmVwbGFjZSA9IEZBTFNFKQ0KDQpzZWxlY3RlZF9kYXRhX211bHRpbm9tX3RyYWluIDwtIHNlbGVjdGVkX2RhdGFfbXVsdGlub21bdHJhaW4sIF0NCnNlbGVjdGVkX2RhdGFfbXVsdGlub21fdGVzdCA8LSBzZWxlY3RlZF9kYXRhX211bHRpbm9tWy10cmFpbiwgXQ0KDQpzZWxlY3RlZF9kYXRhX211bHRpbm9tICU+JQ0KICBtdXRhdGUoaXNfb3V0ZG9vciA9IGFzLm51bWVyaWMoaXNfb3V0ZG9vcikpICU+JQ0KICBtdXRhdGUocV90aGVybWFsX3ByZWZlcmVuY2UgPSBhcy5udW1lcmljKHFfdGhlcm1hbF9wcmVmZXJlbmNlKSkgJT4lDQogIG11dGF0ZShpc193aW50ZXIgPSBhcy5udW1lcmljKGlzX3dpbnRlcikpICU+JQ0KICBtdXRhdGUoaXNfZGF5ID0gYXMubnVtZXJpYyhpc19kYXkpKSAlPiUNCiAgY29yKHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKSAlPiUNCiAgY29ycnBsb3Qob3JkZXIgPSAnYWxwaGFiZXQnLCBkaWFnID0gRikNCmBgYA0KDQpgYGB7cn0NCm1vZGVsX211bHRpbm9tIDwtIG11bHRpbm9tKHFfdGhlcm1hbF9wcmVmZXJlbmNlIH4gLiwgZGF0YSA9IHNlbGVjdGVkX2RhdGFfbXVsdGlub21fdHJhaW4pDQpzdW1tYXJ5KG1vZGVsX211bHRpbm9tKQ0KYGBgDQoNCmBgYHtyfQ0KdGlkeShtb2RlbF9tdWx0aW5vbSwgY29uZi5pbnQgPSBUUlVFKSAlPiUgDQogIGthYmxlKCkgJT4lIA0KICBrYWJsZV9zdHlsaW5nKCJiYXNpYyIsIGZ1bGxfd2lkdGggPSBGQUxTRSkNCmBgYA0KDQpgYGB7cn0NCm1vZGVsX211bHRpbm9tMiA8LSBtdWx0aW5vbShxX3RoZXJtYWxfcHJlZmVyZW5jZSB+IGlzX291dGRvb3IgKyBpc193aW50ZXIgKyB0ZW1wZXJhdHVyZSArIGh1bWlkaXR5ICsgR3JlZW4uVmlldy5NZWFuICsgU2t5LlZpZXcuTWVhbiArIEJ1aWxkaW5nLlZpZXcuTWVhbiArIFJvYWQuVmlldy5NZWFuLCBkYXRhID0gc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90cmFpbikNCnN1bW1hcnkobW9kZWxfbXVsdGlub20pDQpgYGANCg0KYGBge3J9DQp0aWR5KG1vZGVsX211bHRpbm9tMiwgY29uZi5pbnQgPSBUUlVFKSAlPiUgDQogIGthYmxlKCkgJT4lIA0KICBrYWJsZV9zdHlsaW5nKCJiYXNpYyIsIGZ1bGxfd2lkdGggPSBGQUxTRSkNCmBgYA0KDQoNCmBgYHtyfQ0KcHJlZGljdGVkIDwtDQogIHByZWRpY3QobW9kZWxfbXVsdGlub20yLCBzZWxlY3RlZF9kYXRhX211bHRpbm9tX3Rlc3QsIHR5cGU9ImNsYXNzIikNCnN1bShwcmVkaWN0ZWQgPT0gc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0JHFfdGhlcm1hbF9wcmVmZXJlbmNlKSAvIG5yb3coc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0KQ0KcFIyKG1vZGVsX211bHRpbm9tMilbJ01jRmFkZGVuJ10NCg0KTWV0cmljczo6YWNjdXJhY3koc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0JHFfdGhlcm1hbF9wcmVmZXJlbmNlLCBwcmVkaWN0KG1vZGVsX211bHRpbm9tMiwgc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0LCB0eXBlPSJjbGFzcyIpKQ0KYGBgDQpgYGB7cn0NCnRhYmxlKHByZWRpY3QobW9kZWxfbXVsdGlub20yLCBzZWxlY3RlZF9kYXRhX211bHRpbm9tX3Rlc3QsIHR5cGUgPSAiY2xhc3MiKSkNCmBgYA0KDQoNCmBgYHtyfQ0KZml0LnRyZWVfbXVsdGlub20gPSBycGFydChxX3RoZXJtYWxfcHJlZmVyZW5jZSB+IC4sIGRhdGE9c2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90cmFpbiwgbWV0aG9kPSJjbGFzcyIsIGNwPTAuMDA4KQ0KcHJwKGZpdC50cmVlX211bHRpbm9tLA0KICAgIG1haW4gPSAiVHJlZSBtb2RlbCBmb3IgcHJlZGljdGluZyBhY3R1YWwgdGhlcm1hbCBwcmVmZXJlbmNlIiwNCiAgICBib3gucGFsZXR0ZSA9ICJhdXRvIiwNCiAgICBmYWxsZW4ubGVhdmVzID0gRiwgIA0KICAgIHNoYWRvdy5jb2wgPSAiZ3JheSIsICAgDQogICAgYnJhbmNoLmx0eSA9IDMsICAgICAgICANCiAgICBicmFuY2ggPSAuNSwgICAgICAgICAgIA0KICAgIGZhY2xlbiA9IDAsICAgICAgICAgICAgDQogICAgcm91bmQgPSAwKQ0KYGBgDQpgYGB7cn0NCmZpdC50cmVlX211bHRpbm9tJHZhcmlhYmxlLmltcG9ydGFuY2UNCmBgYA0KDQoNCmBgYHtyfQ0KYmVzdGNwX211bHRpbm9tIDwtIGZpdC50cmVlX211bHRpbm9tJGNwdGFibGVbd2hpY2gubWluKGZpdC50cmVlX211bHRpbm9tJGNwdGFibGVbLCJ4ZXJyb3IiXSksIkNQIl0NCmJlc3RjcF9tdWx0aW5vbQ0KZmluYWxfdHJlZV9tb2RlbCA8LSBwcnVuZShmaXQudHJlZV9tdWx0aW5vbSwgY3AgPSBiZXN0Y3BfbXVsdGlub20pDQpmaW5hbF90cmVlX21vZGVsJHZhcmlhYmxlLmltcG9ydGFuY2UNCg0KcHJwKGZpbmFsX3RyZWVfbW9kZWwsDQogICAgbWFpbiA9ICJUcmVlIG1vZGVsIGZvciBwcmVkaWN0aW5nIGFjdHVhbCB0aGVybWFsIHByZWZlcmVuY2UiLA0KICAgIGJveC5wYWxldHRlID0gImF1dG8iLA0KICAgIGZhbGxlbi5sZWF2ZXMgPSBGLCAgDQogICAgc2hhZG93LmNvbCA9ICJncmF5IiwgICANCiAgICBicmFuY2gubHR5ID0gMywgICAgICAgIA0KICAgIGJyYW5jaCA9IC41LCAgICAgICAgICAgDQogICAgZmFjbGVuID0gMCwgICAgICAgICAgICANCiAgICByb3VuZCA9IDApDQpgYGANCmBgYHtyfQ0KZmluYWxfdHJlZV9tb2RlbCA8LQ0KICBycGFydCgNCiAgICBxX3RoZXJtYWxfcHJlZmVyZW5jZSB+IA0KICAgICAgVmlzdWFsLkNvbXBsZXhpdHkuTWVhbiArIA0KICAgICAgRm9vdHByaW50Lk1lYW4gKyANCiAgICAgIFNreS5WaWV3Lk1lYW4gKyANCiAgICAgIEdyZWVuLlZpZXcuTWVhbiArIA0KICAgICAgUm9hZC5WaWV3Lk1lYW4gKyANCiAgICAgIFNreS5WaWV3Lk1lYW4gKyANCiAgICAgIHRlbXBlcmF0dXJlICsNCiAgICAgIGh1bWlkaXR5ICsgDQogICAgICBpc19vdXRkb29yLA0KICAgIGRhdGEgPSBzZWxlY3RlZF9kYXRhX211bHRpbm9tX3RyYWluLA0KICAgIG1ldGhvZCA9ICJjbGFzcyIsDQogICAgY3AgPSAwLjAwOA0KICApDQpwcnAoZmluYWxfdHJlZV9tb2RlbCwNCiAgICBtYWluID0gIlRyZWUgbW9kZWwgZm9yIHByZWRpY3RpbmcgYWN0dWFsIHRoZXJtYWwgcHJlZmVyZW5jZSIsDQogICAgYm94LnBhbGV0dGUgPSAiYXV0byIsDQogICAgZmFsbGVuLmxlYXZlcyA9IEYsICANCiAgICBzaGFkb3cuY29sID0gImdyYXkiLCAgIA0KICAgIGJyYW5jaC5sdHkgPSAzLCAgICAgICAgDQogICAgYnJhbmNoID0gLjUsICAgICAgICAgICANCiAgICBmYWNsZW4gPSAwLCAgICAgICAgICAgIA0KICAgIHJvdW5kID0gMCkNCmBgYA0KDQoNCmBgYHtyfQ0KcHJlZGljdGVkIDwtIGFzLm51bWVyaWMocHJlZGljdChmaW5hbF90cmVlX21vZGVsLCBzZWxlY3RlZF9kYXRhX211bHRpbm9tX3Rlc3QsIHR5cGUgPSAiY2xhc3MiKSkNCnN1bShwcmVkaWN0ZWQgPT0gYXMubnVtZXJpYyhzZWxlY3RlZF9kYXRhX211bHRpbm9tX3Rlc3QkcV90aGVybWFsX3ByZWZlcmVuY2UpKSAvIG5yb3coc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0KQ0KYGBgDQoNCmBgYHtyfQ0KdGFibGUocHJlZGljdChmaW5hbF90cmVlX21vZGVsLCBzZWxlY3RlZF9kYXRhX211bHRpbm9tX3Rlc3QsIHR5cGUgPSAiY2xhc3MiKSkNCmBgYA0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNTApDQoNCm1vZGVsX2ZvcmVzdF9tdWx0aW5vbSA8LQ0KICByYW5kb21Gb3Jlc3QoDQogICAgcV90aGVybWFsX3ByZWZlcmVuY2UgfiAuIC1hdmVyYWdlX2hlYXJ0X3JhdGUgLWRpc3Rfd2Fsa2VkIC1yYWluZmFsbCAtd2luZF9zcGVlZCwNCiAgICBkYXRhID0gc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90cmFpbiwNCiAgICBpbXBvcnRhbmNlID0gVFJVRSwNCiAgICBudHJlZSA9IDIwMA0KICApDQoNCnByZWRpY3RlZCA8LSBhcy5udW1lcmljKHByZWRpY3QobW9kZWxfZm9yZXN0X211bHRpbm9tLCBzZWxlY3RlZF9kYXRhX211bHRpbm9tX3Rlc3QpKQ0Kc3VtKHByZWRpY3RlZCA9PSBhcy5udW1lcmljKHNlbGVjdGVkX2RhdGFfbXVsdGlub21fdGVzdCRxX3RoZXJtYWxfcHJlZmVyZW5jZSkpIC8gbnJvdyhzZWxlY3RlZF9kYXRhX211bHRpbm9tX3Rlc3QpDQpgYGANCmBgYHtyfQ0KcmFuZG9tRm9yZXN0OjppbXBvcnRhbmNlKG1vZGVsX2ZvcmVzdF9tdWx0aW5vbSkNCmBgYA0KYGBge3J9DQp0YWJsZShwcmVkaWN0KG1vZGVsX2ZvcmVzdF9tdWx0aW5vbSwgc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0LCB0eXBlID0gImNsYXNzIikpDQpgYGANCmBgYHtyfQ0KdGFibGUoc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0JHFfdGhlcm1hbF9wcmVmZXJlbmNlKQ0KYGBgDQpgYGB7cn0NCiMgaWYgd2UgcHJlZGljdCAiTm8gQ2hhbmdlIiB3ZSB3aWxsIGJlIGNvcnJlY3QgNTElIG9mIHRoZSB0aW1lDQpzdW0oc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0JHFfdGhlcm1hbF9wcmVmZXJlbmNlID09ICJObyBjaGFuZ2UiKSAvIG5yb3coc2VsZWN0ZWRfZGF0YV9tdWx0aW5vbV90ZXN0KQ0KYGBgDQoNCj09PT09PT09PT09PT09PT09PT09PT09PU1vZGVsIFRlc3RpbmcgRW5kcyBIZXJlPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==